I'm trying to perform a Log-polar transform on an image for the purpose of image registration. In other words I'm trying to achieve this:
------>
I need to code this from scratch in Java because I'll be doing this on the GPU side with OpenCL Java bindings and can't use libraries. There are multiple threads on this but none that could really help me, mostly because they're all using in-built MATLAB functions that I cannot replicate.
I've been trying the Polar Transform instead of the Log-Polar Transform for the sake of getting this to work because most info online refers to the first. So far, the best result I've had is with this bit here (pseudocode), based on this thread:
w = input.width; // Width of the input image
h = input.height; // Height of the input image
// Copy input pixels into an array
for(y=0; y<h; y++){
for(x=0; x<w; x++){
input[y*w+x] = getPixel(x, y);
}
}
// Polar transform
maxRadius = sqrt(w*w + h*h);
radiusScale = w / maxRadius;
angleScale = h / (2 * PI);
for(y=0; y<h; y++){
dy = y - h/2; // Distance from the center in the y-axis
for(x=0; x<w; x++){
dx = x - w/2; // Distance from the center in the x-axis
angle = atan2(dy, dx) % (2*PI);
radius = sqrt(dx*dx + dy*dy);
newY = radius * radiusScale;
newX = radius * thetaScale;
output[y*w+x] = input[newY*w+newX];
}
}
What I get resembles some sort of polar transformation, despite not being the result that I'm looking for:
output image
Can someone give me any pointers on this?
Thanks
EDIT:
The log-polar transform goes like .
EDIT:
Implementing #matt suggestions I now have the following code:
w = input.width; // Width of the input image
h = input.height; // Height of the input image
maxRadius = sqrt(w*w/4 + h*h/4);
radiusScale = h / maxRadius;
angleScale = w /PI/2;
offset = PI;
for(y=0; y<h; y++){
dy = y - h/2; // Distance from center in the y-axis
for(x=0; x<w; x++){
dx = x - w/2; // Distance from the center in the x-axis
angle = atan2(dy, dx);
radius = sqrt(dx*dx + dy*dy);
newY = radius * radiusScale;
newX = (angle + offset) * angleScale;
output[newY*w+newX] = input.getPixel(x, y);
}
}
Plotting the new output gives me this, which is still not what I expect to get.
One issue with this transform is that each "pixel" in the transformed space takes up a different amount of space in the x,y space. So here is how I am defining the transform.
Our new image will have the same dimensions
The Y axis will be 0 to Max Rho
The X axis will be -PI to +PI
So we start by iterating over the i,j coordinates of the output image. The resulting rho and theta are as follows.
double rho = j*maxRho / height;
double theta = i*maxTheta / width + offset;
Now we need to grab the input pixels at the respective location.
int x = (int) ( Math.exp(rho)*Math.cos(theta) + width/2);
int y = (int) ( Math.exp(rho)*Math.sin(theta) + height/2);
Now we can get the pixel value.
int pixel = input[y*width + x];
Your input variable is a bit redundante since you could just use your getPixel(x, y)
Then we just set the corresponding output value.
output[j*width + i] = pixel;
Here is a compilable example.
import javax.imageio.ImageIO;
import java.net.URL;
import java.awt.image.BufferedImage;
import java.io.File;
public class ScannerJunk{
public static void main(String[] args) throws Exception{
BufferedImage img = ImageIO.read( new URL("https://i.stack.imgur.com/MvDQT.png") );
BufferedImage out = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
double w = img.getWidth();
double h = img.getHeight();
double maxRho = 0.5*Math.log( w*w/4.0 + h*h/4.0 );
double maxTheta = 2 * Math.PI;
double offset = - Math.PI;
for(int i = 0; i< w; i++){
for(int j = 0; j< h; j++){
double rho = j*maxRho / h;
double theta = i*maxTheta / w + offset;
int x = (int) ( Math.exp(rho)*Math.cos(theta) + w/2);
int y = (int) ( Math.exp(rho)*Math.sin(theta) + h/2);
try{
out.setRGB( i, j, img.getRGB( x, y ) );
} catch(Exception e){
System.out.println( i + ", " + j + " :: " + rho + ", " + theta + " :: " + x + ", " + y );
}
}
}
ImageIO.write(out, "PNG", new File("transformed.png") );
}
}
Note that the max radius, does not map to the input image for all of the possible angles.
Also, the radius and theta axis appeat to be transposed from your example image.
Related
So I'm trying to make a hexagon using the points rsin(theta) and rcos(theta) formulas but seem to have some error in the formula. When writing it out on paper formula works but feel like I'm overlooking something....
public static void drawHexagon(double len, double xc, double yc, int angle)
{
if(angle <= -360)
angle = 0;
double rotAngX = (len*Math.cos(angle));
double rotAngY = (len*Math.sin(angle));
//System.out.println(rotAngX + " " + rotAngY + " " + Math.cos(-30));
/*double [] x = {(xc - len*(Math.sqrt(3)/2.0)), xc,
xc + len*(Math.sqrt(3)/2.0), xc + len*(Math.sqrt(3)/2.0),
xc, xc - len*(Math.sqrt(3)/2.0)};
double [] y = {yc + len/2.0, yc + len, yc + len/2.0,
yc - len/2.0, yc - len, yc - len/2.0};*/
double[] x = new double[6];
double[] y = new double[6];
int[] angles = { 150, 90, 30, 330, 270, 210 };
for(int i = 0; i < 6; i++) {
x[i] = xc + (len*Math.cos(angles[i]));
y[i] = yc + (len*Math.sin(angles[i]));
}
printPoints(x, y);
StdDraw.setPenColor(Color.CYAN);
StdDraw.filledPolygon(x,y);
StdDraw.setPenColor(Color.PINK);
StdDraw.polygon(x,y);
}
The section commented out works but I'm trying to rotate them, so in the for loop will be adding the angle parameter to angles[i]. What am I doing wrong in the for loop?
Here's the result of the code. Hexagon but not really:
The Javadoc of Math.sin says:
public static double sin(double a)
Returns the trigonometric sine of an angle. [...]
Parameters:
a - an angle, in radians.
Your code passes the angle in degrees rather than radians.
You can either convert the angle first:
Math.sin(Math.toRadians(30))
or directly specify the angle in radians:
Math.sin(Math.PI / 6);
I am not sure what you are trying to do, but is it this?:
public static void drawHexagon(double len, double xc, double yc, int angle)
{
double[] x = new double[6];
double[] y = new double[6];
x[0] = len*Math.cos(3.14*angle/180.0);
y[0] = len*Math.sin(3.14*angle/180.0);
double cos_60;
double sin_60;
cos_60 = 1.0/2.0;
sin_60 = Math.sqrt(3.0)/2.0;
for(int i = 1; i < 6; i++) {
x[i] = xc + cos_60*x[i-1] - sin_60*y[i-1];
y[i] = yc + sin_60*x[i-1] + cos_60*y[i-1];
}
printPoints(x, y);
StdDraw.setPenColor(Color.CYAN);
StdDraw.filledPolygon(x,y);
StdDraw.setPenColor(Color.PINK);
StdDraw.polygon(x,y);
}
I want to create a "spiral effect" with particles (or any entities) in Java.
I'm new to objective programming (and also Java), so I started with something easier. I firstly created a Path object that has a value of Locations[] signed to it, it gets from the user a: Start location, End location, and double value, that tells him, how much space between each location in the path he has.
private void setLocations() {
//initialize vars
Location start = getStart();
World world = start.getWorld();
Location[] locations = new Location[amount];
double x = start.getX();
double y = start.getY();
double z = start.getZ();
//loop that will set values for locations
for (int i = 0; i < amount; i++) {
locations[i] = new Location(
world,
x + dividedDistanceX * (i + 1),
y + dividedDistanceY * (i + 1),
z + dividedDistanceZ * (i + 1)
);
}
this.locations = locations;
}
Now you might be asking what is the amount? So simply it's the number of points that are created when the object is initialized. It's simple math like getting the longest distance from point to point, and then dividing it by the value of space between each point.
Now the situation gets a little more complicated, so I prepared graphics for you:)
I want to rotate points around the longest axis to form some form of a spiral, and I want from user to set the maximum distance between the starting point and the new one.
Something like this:
And another graph of the sinusoid around one vector (x, y)
Honestly, I need some help.
Here's GitHub object link
Things I know I need to do:
Get the axis around which I will rotate point (it's the longest distance between points)
Add some value to the rest values (x+something, y+something)
Add angle, that point will rotate with, (for example each point will be rotated by 22,5).
Okay, so i did it, it wasn't even that hard:
public Location[] createSpiral(double radius, float angle, Location[] path) {
final int length = path.length;
Location[] result = path.clone();
Location start = path[0];
Location end = path[length - 1];
double startX = start.getX();
double startY = start.getY();
double startZ = start.getZ();
double endX = end.getX();
double endY = end.getY();
double endZ = end.getZ();
double distanceX = setDistance(startX, endX);
double distanceY = setDistance(startY, endY);
double distanceZ = setDistance(startZ, endZ);
double highestOffset = getHighestOffset(new double[]{distanceX, distanceY, distanceZ});
if (highestOffset == abs(distanceX)) {
for (int i = 0; i < length; i++) {
double sin = radius * sin(angle * i / length);
double cos = radius * cos(angle * i / length);
result[i].setY(result[i].getY() + cos);
result[i].setZ(result[i].getZ() + sin);
}
} else if (highestOffset == abs(distanceY)) {
for (int i = 0; i < length; i++) {
double sin = radius * sin(angle * i / length);
double cos = radius * cos(angle * i / length);
result[i].setX(result[i].getX() + cos);
result[i].setZ(result[i].getZ() + sin);
}
} else if (highestOffset == abs(distanceZ)) {
for (int i = 0; i < length; i++) {
double sin = radius * sin(angle * i / length);
double cos = radius * cos(angle * i / length);
result[i].setX(result[i].getX() + cos);
result[i].setY(result[i].getY() + sin);
}
} else {
return path;
}
return result;
}
It's just
double sin = radius * sin(angle * i / length);
double cos = radius * cos(angle * i / length);
and adding those values to corresponding X, Y if Z has the highest distance from a location, etc.
The rest of the code and methods are located in the GitHub link above.
I am going to try my best to give context for the below code. This is a method used to draw a circle and its center point in a 50x50 white square background. The following variables were used:
xc,yx - the center coordinates used to compute the circle
r - the radius of the circle
STEP - how often a new point is drawn on the circumference of the circle
x,y - the coordinates of each point that will make up the circle
Right now, my method uses a for loop to compute each points R,G, and B coordinates along the circumference of the circle based on the center point and the radius. What I am trying to do is anti-alias my output circle so that the round parts are not as jagged. However, I want to do this using only math and variables and I do not want to use any of Java's build in methods. Thank you to anyone who can help or point me in the right direction.
Below is my routine:
protected void proc_21() {
info = "Draw anti-aliased circle";
int xc = (int) rand(1, imgW - 2);
int yc = (int) rand(1, imgH - 2);
int r = (int) rand(4, 0.35f * (imgW + imgH));
int STEP = (2 * (int) Math.PI * r) * 57;
System.out.printf("circle centered at (%d,%d), radius = %d, draw in %d steps. \n", xc,yc,r,STEP);
for (int i = 0; i < STEP; i++) {
int x = (int) Math.round(xc + r * Math.cos(i));
int y = (int) Math.round(yc + r * Math.sin(i));
if (0 <= x && x < imgW) {
if ( 0 <= y && y < imgH) {
imgNew.setR(x, y, 0);
imgNew.setG(x, y, 0);
imgNew.setB(x, y, 1);
}
}
}
// set center to red
imgNew.setR(xc, yc, 1);
imgNew.setG(xc, yc, 0);
imgNew.setB(xc, yc, 0);
}
I am trying to run video on 3d surfaces in android.
I am able to run it properly on a squareso I proceeded for a sphere.
I found multiple algorithms and functions to generate sphere vertices and tex coords with or without indexes and tried them.
Below are the two functions that are partially working
1st gives improperly mapped textures
public void sphere(final int depth, final float radius) {
// Clamp depth to the range 1 to MAXIMUM_ALLOWED_DEPTH;
final int d = Math.max(1, Math.min(MAXIMUM_ALLOWED_DEPTH, depth));
// Calculate basic values for the sphere.
this.mTotalNumStrips = power(2, d - 1) * VERTEX_MAGIC_NUMBER;
numVerticesPerStrip = power(2, d) * 3;
final double altitudeStepAngle = ONE_TWENTY_DEGREES / power(2, d);
final double azimuthStepAngle = THREE_SIXTY_DEGREES / mTotalNumStrips;
double x, y, z, h, altitude, azimuth; int vertexPos = 0;
int texturePos = 0;
//textureBuffer= new ArrayList<FloatBuffer>();
/** Mapping texture coordinates for the vertices. */
//mTexture = new ArrayList<float[]>();
//mVertices= new ArrayList<float[]>();
//mVertices = new float[numVerticesPerStrip * NUM_FLOATS_PER_VERTEX *mTotalNumStrips]; // NOPMD
// mTexture = new float[numVerticesPerStrip * NUM_FLOATS_PER_TEXTURE * mTotalNumStrips]; // NOPMD
/*for (int stripNum = 0; stripNum < this.mTotalNumStrips; stripNum++) {
// Setup arrays to hold the points for this strip.
// Calculate position of the first vertex in this strip.
altitude = NINETY_DEGREES;
azimuth = stripNum * azimuthStepAngle;
// Draw the rest of this strip.
for (int vertexNum = 0; vertexNum < numVerticesPerStrip; vertexNum += 2) {
// First point - Vertex.
y = radius * Math.sin(altitude);
h = radius * Math.cos(altitude);
z = h * Math.sin(azimuth);
x = h * Math.cos(azimuth);
mVertices[vertexPos++] = (float) x;
mVertices[vertexPos++] = (float) y;
mVertices[vertexPos++] = (float) z;
// First point - Texture.
mTexture[texturePos++] = (float) (1 - azimuth / THREE_SIXTY_DEGREES);
mTexture[texturePos++] = (float) (1 - (altitude + NINETY_DEGREES) / ONE_EIGHTY_DEGREES);
// Second point - Vertex.
altitude -= altitudeStepAngle;
azimuth -= azimuthStepAngle / 2.0;
y = radius * Math.sin(altitude);
h = radius * Math.cos(altitude);
z = h * Math.sin(azimuth);
x = h * Math.cos(azimuth);
mVertices[vertexPos++] = (float) x;
mVertices[vertexPos++] = (float) y;
mVertices[vertexPos++] = (float) z;
// Second point - Texture.
mTexture[texturePos++] = (float) (1 - azimuth / THREE_SIXTY_DEGREES);
mTexture[texturePos++] = (float) (1 - (altitude + NINETY_DEGREES) / ONE_EIGHTY_DEGREES);
azimuth += azimuthStepAngle;
}
*/
mVertices = new float[numVerticesPerStrip * NUM_FLOATS_PER_VERTEX *mTotalNumStrips]; // NOPMD
mTexture = new float[numVerticesPerStrip * NUM_FLOATS_PER_TEXTURE*mTotalNumStrips]; // NOPMD
for (int stripNum = 0; stripNum < this.mTotalNumStrips; stripNum++) {
// Setup arrays to hold the points for this strip.
// int vertexPos = 0;
// int texturePos = 0;
// Calculate position of the first vertex in this strip.
altitude = NINETY_DEGREES;
azimuth = stripNum * azimuthStepAngle;
// Draw the rest of this strip.
for (int vertexNum = 0; vertexNum < numVerticesPerStrip; vertexNum += 2) {
// First point - Vertex.
y = radius * Math.sin(altitude);
h = radius * Math.cos(altitude);
z = h * Math.sin(azimuth);
x = h * Math.cos(azimuth);
mVertices[vertexPos++] = (float) x;
mVertices[vertexPos++] = (float) y;
mVertices[vertexPos++] = (float) z;
// First point - Texture.
mTexture[texturePos++] = (float) (1.0 - azimuth / THREE_SIXTY_DEGREES);
mTexture[texturePos++] = (float) (1.0 - (altitude + NINETY_DEGREES) / ONE_EIGHTY_DEGREES);
// Second point - Vertex.
altitude -= altitudeStepAngle;
azimuth -= azimuthStepAngle / 2.0;
y = radius * Math.sin(altitude);
h = radius * Math.cos(altitude);
z = h * Math.sin(azimuth);
x = h * Math.cos(azimuth);
mVertices[vertexPos++] = (float) x;
mVertices[vertexPos++] = (float) y;
mVertices[vertexPos++] = (float) z;
// Second point - Texture.
mTexture[texturePos++] = (float) (1.0 - azimuth / THREE_SIXTY_DEGREES);
mTexture[texturePos++] = (float) (1.0 - (altitude + NINETY_DEGREES) / ONE_EIGHTY_DEGREES);
azimuth += azimuthStepAngle;
}
// this.mVertices.add(mVertices);
// this.mTexture.add(textureBuffer);
}
}
The 2nd working function gives me only half sphere on right side
The function is as below
public void Sphere3D(//context:Context3D,
int slices,
int stacks)
// double posX, double posY,double posZ,
// double scaleX, double scaleY,double scaleZ)
{
// Make the model->world transformation matrix to position and scale the sphere
// Cap parameters
if (slices < MIN_SLICES)
{
slices = MIN_SLICES;
}
if (stacks < MIN_STACKS)
{
stacks = MIN_STACKS;
}
// Data we will later upload to the GPU
//var positions:Vector.<Number>;
//var texCoords:Vector.<Number>;
//var tris:Vector.<uint>;
// Pre-compute many constants used in tesselation
final double stepTheta = (2.0*Math.PI) / slices;
final double stepPhi = Math.PI / stacks;
final double stepU = 1.0 / slices;
final double stepV = 1.0 / stacks;
final int verticesPerStack = slices + 1;
final int numVertices = verticesPerStack * (stacks+1);
// Allocate the vectors of data to tesselate into
//positions = new Vector.<Number>(numVertices*3);
mVertices=new float[numVertices*3];
//texCoords = new Vector.<Number>(numVertices*2);
mTexture=new float[numVertices*2];
//tris = new Vector.<uint>(slices*stacks*6);
mIndexes= new short[slices*stacks*6];
// Pre-compute half the sin/cos of thetas
double halfCosThetas[] = new double[verticesPerStack];
double halfSinThetas[] = new double[verticesPerStack];
int curTheta= 0;
for (int slice=0; slice < verticesPerStack; ++slice)
{
halfCosThetas[slice] = Math.cos(curTheta) * 0.5;
halfSinThetas[slice] = Math.sin(curTheta) * 0.5;
curTheta += stepTheta;
}
// Generate positions and texture coordinates
double curV = 1.0;
double curPhi = Math.PI;
int posIndex=0;
int texCoordIndex=0;
for (int stack = 0; stack < stacks+1; ++stack)
{
double curU = 1.0;
double curY = Math.cos(curPhi) * 0.5;
double sinCurPhi = Math.sin(curPhi);
for (int slice = 0; slice < verticesPerStack; ++slice)
{
mVertices[posIndex++] = (float)(halfCosThetas[slice]*sinCurPhi);
mVertices[posIndex++] =(float) curY;
mVertices[posIndex++] = (float)(halfSinThetas[slice] * sinCurPhi);
mTexture[texCoordIndex++] = (float)curU;
mTexture[texCoordIndex++] = (float)curV;
curU -= stepU;
}
curV -= stepV;
curPhi -= stepPhi;
}
// Generate tris
int lastStackFirstVertexIndex= 0;
int curStackFirstVertexIndex = verticesPerStack;
int triIndex=0;
for (int stack = 0; stack < stacks; ++stack)
{
for (int slice = 0; slice < slices; ++slice)
{
// Bottom tri of the quad
mIndexes[triIndex++] = (short)(lastStackFirstVertexIndex + slice + 1);
mIndexes[triIndex++] = (short)(curStackFirstVertexIndex + slice);
mIndexes[triIndex++] = (short)(lastStackFirstVertexIndex + slice);
// Top tri of the quad
mIndexes[triIndex++] =(short)( lastStackFirstVertexIndex + slice + 1);
mIndexes[triIndex++] =(short)( curStackFirstVertexIndex + slice + 1);
mIndexes[triIndex++] =(short)( curStackFirstVertexIndex + slice);
}
lastStackFirstVertexIndex += verticesPerStack;
curStackFirstVertexIndex += verticesPerStack;
}
// Create vertex and index buffers
/*this.positions = context.createVertexBuffer(positions.length/3, 3);
this.positions.uploadFromVector(positions, 0, positions.length/3);
this.texCoords = context.createVertexBuffer(texCoords.length/2, 2);
this.texCoords.uploadFromVector(texCoords, 0, texCoords.length/2);
this.tris = context.createIndexBuffer(tris.length);
this.tris.uploadFromVector(tris, 0, tris.length);*/
}
what I need is mVertices , mIndees and mTexture to be filled with vertices , indices and texture respectively and if the function does not create indexed coordinates I am drawing normally.
I have been trying to understand the algorithm and detect the issue in both of them but unable to get any leads.
Please let me know if further information is required
I'm trying to convert a lat/long point into a 2d point so that I can display it on an image of the world-which is a mercator projection.
I've seen various ways of doing this and a few questions on stack overflow-I've tried out the different code snippets and although I get the correct longitude to pixel, the latitude is always off-seems to be getting more reasonable though.
I need the formula to take into account the image size, width etc.
I've tried this piece of code:
double minLat = -85.05112878;
double minLong = -180;
double maxLat = 85.05112878;
double maxLong = 180;
// Map image size (in points)
double mapHeight = 768.0;
double mapWidth = 991.0;
// Determine the map scale (points per degree)
double xScale = mapWidth/ (maxLong - minLong);
double yScale = mapHeight / (maxLat - minLat);
// position of map image for point
double x = (lon - minLong) * xScale;
double y = - (lat + minLat) * yScale;
System.out.println("final coords: " + x + " " + y);
The latitude seems to be off by about 30px in the example I'm trying. Any help or advice?
Update
Based on this question:Lat/lon to xy
I've tried to use the code provided but I'm still having some problems with latitude conversion, longitude is fine.
int mapWidth = 991;
int mapHeight = 768;
double mapLonLeft = -180;
double mapLonRight = 180;
double mapLonDelta = mapLonRight - mapLonLeft;
double mapLatBottom = -85.05112878;
double mapLatBottomDegree = mapLatBottom * Math.PI / 180;
double worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
double mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
double x = (lon - mapLonLeft) * (mapWidth / mapLonDelta);
double y = 0.1;
if (lat < 0) {
lat = lat * Math.PI / 180;
y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
} else if (lat > 0) {
lat = lat * Math.PI / 180;
lat = lat * -1;
y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
System.out.println("y before minus: " + y);
y = mapHeight - y;
} else {
y = mapHeight / 2;
}
System.out.println(x);
System.out.println(y);
When using the original code if the latitude value is positive it returned a negative point, so I modified it slightly and tested with the extreme latitudes-which should be point 0 and point 766, it works fine. However when I try a different latitude value ex: 58.07 (just north of the UK) it displays as north of Spain.
The Mercator map projection is a special limiting case of the Lambert Conic Conformal map projection with
the equator as the single standard parallel. All other parallels of latitude are straight lines and the meridians
are also straight lines at right angles to the equator, equally spaced. It is the basis for the transverse and
oblique forms of the projection. It is little used for land mapping purposes but is in almost universal use for
navigation charts. As well as being conformal, it has the particular property that straight lines drawn on it are
lines of constant bearing. Thus navigators may derive their course from the angle the straight course line
makes with the meridians. [1.]
The formulas to derive projected Easting and Northing coordinates from spherical latitude φ and longitude λ
are:
E = FE + R (λ – λₒ)
N = FN + R ln[tan(π/4 + φ/2)]
where λO is the longitude of natural origin and FE and FN are false easting and false northing.
In spherical Mercator those values are actually not used, so you can simplify the formula to
Pseudo code example, so this can be adapted to every programming language.
latitude = 41.145556; // (φ)
longitude = -73.995; // (λ)
mapWidth = 200;
mapHeight = 100;
// get x value
x = (longitude+180)*(mapWidth/360)
// convert from degrees to radians
latRad = latitude*PI/180;
// get y value
mercN = ln(tan((PI/4)+(latRad/2)));
y = (mapHeight/2)-(mapWidth*mercN/(2*PI));
Sources:
OGP Geomatics Committee, Guidance Note Number 7, part 2: Coordinate Conversions and Transformation
Derivation of the Mercator projection
National Atlas: Map Projections
Mercator Map projection
EDIT
Created a working example in PHP (because I suck at Java)
https://github.com/mfeldheim/mapStuff.git
EDIT2
Nice animation of the Mercator projection
https://amp-reddit-com.cdn.ampproject.org/v/s/amp.reddit.com/r/educationalgifs/comments/5lhk8y/how_the_mercator_projection_distorts_the_poles/?usqp=mq331AQJCAEoAVgBgAEB&_js_v=0.1
You cannot merely transpose from longitude/latitude to x/y like that because the world isn't flat. Have you look at this post? Converting longitude/latitude to X/Y coordinate
UPDATE - 1/18/13
I decided to give this a stab, and here's how I do it:-
public class MapService {
// CHANGE THIS: the output path of the image to be created
private static final String IMAGE_FILE_PATH = "/some/user/path/map.png";
// CHANGE THIS: image width in pixel
private static final int IMAGE_WIDTH_IN_PX = 300;
// CHANGE THIS: image height in pixel
private static final int IMAGE_HEIGHT_IN_PX = 500;
// CHANGE THIS: minimum padding in pixel
private static final int MINIMUM_IMAGE_PADDING_IN_PX = 50;
// formula for quarter PI
private final static double QUARTERPI = Math.PI / 4.0;
// some service that provides the county boundaries data in longitude and latitude
private CountyService countyService;
public void run() throws Exception {
// configuring the buffered image and graphics to draw the map
BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDTH_IN_PX,
IMAGE_HEIGHT_IN_PX,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = bufferedImage.createGraphics();
Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints renderHints = new RenderingHints(map);
g.setRenderingHints(renderHints);
// min and max coordinates, used in the computation below
Point2D.Double minXY = new Point2D.Double(-1, -1);
Point2D.Double maxXY = new Point2D.Double(-1, -1);
// a list of counties where each county contains a list of coordinates that form the county boundary
Collection<Collection<Point2D.Double>> countyBoundaries = new ArrayList<Collection<Point2D.Double>>();
// for every county, convert the longitude/latitude to X/Y using Mercator projection formula
for (County county : countyService.getAllCounties()) {
Collection<Point2D.Double> lonLat = new ArrayList<Point2D.Double>();
for (CountyBoundary countyBoundary : county.getCountyBoundaries()) {
// convert to radian
double longitude = countyBoundary.getLongitude() * Math.PI / 180;
double latitude = countyBoundary.getLatitude() * Math.PI / 180;
Point2D.Double xy = new Point2D.Double();
xy.x = longitude;
xy.y = Math.log(Math.tan(QUARTERPI + 0.5 * latitude));
// The reason we need to determine the min X and Y values is because in order to draw the map,
// we need to offset the position so that there will be no negative X and Y values
minXY.x = (minXY.x == -1) ? xy.x : Math.min(minXY.x, xy.x);
minXY.y = (minXY.y == -1) ? xy.y : Math.min(minXY.y, xy.y);
lonLat.add(xy);
}
countyBoundaries.add(lonLat);
}
// readjust coordinate to ensure there are no negative values
for (Collection<Point2D.Double> points : countyBoundaries) {
for (Point2D.Double point : points) {
point.x = point.x - minXY.x;
point.y = point.y - minXY.y;
// now, we need to keep track the max X and Y values
maxXY.x = (maxXY.x == -1) ? point.x : Math.max(maxXY.x, point.x);
maxXY.y = (maxXY.y == -1) ? point.y : Math.max(maxXY.y, point.y);
}
}
int paddingBothSides = MINIMUM_IMAGE_PADDING_IN_PX * 2;
// the actual drawing space for the map on the image
int mapWidth = IMAGE_WIDTH_IN_PX - paddingBothSides;
int mapHeight = IMAGE_HEIGHT_IN_PX - paddingBothSides;
// determine the width and height ratio because we need to magnify the map to fit into the given image dimension
double mapWidthRatio = mapWidth / maxXY.x;
double mapHeightRatio = mapHeight / maxXY.y;
// using different ratios for width and height will cause the map to be stretched. So, we have to determine
// the global ratio that will perfectly fit into the given image dimension
double globalRatio = Math.min(mapWidthRatio, mapHeightRatio);
// now we need to readjust the padding to ensure the map is always drawn on the center of the given image dimension
double heightPadding = (IMAGE_HEIGHT_IN_PX - (globalRatio * maxXY.y)) / 2;
double widthPadding = (IMAGE_WIDTH_IN_PX - (globalRatio * maxXY.x)) / 2;
// for each country, draw the boundary using polygon
for (Collection<Point2D.Double> points : countyBoundaries) {
Polygon polygon = new Polygon();
for (Point2D.Double point : points) {
int adjustedX = (int) (widthPadding + (point.getX() * globalRatio));
// need to invert the Y since 0,0 starts at top left
int adjustedY = (int) (IMAGE_HEIGHT_IN_PX - heightPadding - (point.getY() * globalRatio));
polygon.addPoint(adjustedX, adjustedY);
}
g.drawPolygon(polygon);
}
// create the image file
ImageIO.write(bufferedImage, "PNG", new File(IMAGE_FILE_PATH));
}
}
RESULT: Image width = 600px, Image height = 600px, Image padding = 50px
RESULT: Image width = 300px, Image height = 500px, Image padding = 50px
Java version of original Google Maps JavaScript API v3 java script code is as following, it works with no problem
public final class GoogleMapsProjection2
{
private final int TILE_SIZE = 256;
private PointF _pixelOrigin;
private double _pixelsPerLonDegree;
private double _pixelsPerLonRadian;
public GoogleMapsProjection2()
{
this._pixelOrigin = new PointF(TILE_SIZE / 2.0,TILE_SIZE / 2.0);
this._pixelsPerLonDegree = TILE_SIZE / 360.0;
this._pixelsPerLonRadian = TILE_SIZE / (2 * Math.PI);
}
double bound(double val, double valMin, double valMax)
{
double res;
res = Math.max(val, valMin);
res = Math.min(res, valMax);
return res;
}
double degreesToRadians(double deg)
{
return deg * (Math.PI / 180);
}
double radiansToDegrees(double rad)
{
return rad / (Math.PI / 180);
}
PointF fromLatLngToPoint(double lat, double lng, int zoom)
{
PointF point = new PointF(0, 0);
point.x = _pixelOrigin.x + lng * _pixelsPerLonDegree;
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
double siny = bound(Math.sin(degreesToRadians(lat)), -0.9999,0.9999);
point.y = _pixelOrigin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) *- _pixelsPerLonRadian;
int numTiles = 1 << zoom;
point.x = point.x * numTiles;
point.y = point.y * numTiles;
return point;
}
PointF fromPointToLatLng(PointF point, int zoom)
{
int numTiles = 1 << zoom;
point.x = point.x / numTiles;
point.y = point.y / numTiles;
double lng = (point.x - _pixelOrigin.x) / _pixelsPerLonDegree;
double latRadians = (point.y - _pixelOrigin.y) / - _pixelsPerLonRadian;
double lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
return new PointF(lat, lng);
}
public static void main(String []args)
{
GoogleMapsProjection2 gmap2 = new GoogleMapsProjection2();
PointF point1 = gmap2.fromLatLngToPoint(41.850033, -87.6500523, 15);
System.out.println(point1.x+" "+point1.y);
PointF point2 = gmap2.fromPointToLatLng(point1,15);
System.out.println(point2.x+" "+point2.y);
}
}
public final class PointF
{
public double x;
public double y;
public PointF(double x, double y)
{
this.x = x;
this.y = y;
}
}
JAVA only?
Python code here! Refer to Convert latitude/longitude point to a pixels (x,y) on mercator projection
import math
from numpy import log as ln
# Define the size of map
mapWidth = 200
mapHeight = 100
def convert(latitude, longitude):
# get x value
x = (longitude + 180) * (mapWidth / 360)
# convert from degrees to radians
latRad = (latitude * math.pi) / 180
# get y value
mercN = ln(math.tan((math.pi / 4) + (latRad / 2)))
y = (mapHeight / 2) - (mapWidth * mercN / (2 * math.pi))
return x, y
print(convert(41.145556, 121.2322))
Answer:
(167.35122222222225, 24.877939817552335)
public static String getTileNumber(final double lat, final double lon, final int zoom) {
int xtile = (int)Math.floor( (lon + 180) / 360 * (1<<zoom) ) ;
int ytile = (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom) ) ;
if (xtile < 0)
xtile=0;
if (xtile >= (1<<zoom))
xtile=((1<<zoom)-1);
if (ytile < 0)
ytile=0;
if (ytile >= (1<<zoom))
ytile=((1<<zoom)-1);
return("" + zoom + "/" + xtile + "/" + ytile);
}
}
I'm new here, just to write, as I've been following the community for some years. I'm happy to be able to contribute.
Well, it took me practically a day in search of that and your question encouraged me to continue the search.
I arrived at the following function, which works! Credits for this article: https://towardsdatascience.com/geotiff-coordinate-querying-with-javascript-5e6caaaf88cf
var bbox = [minLong, minLat, maxLong, maxLat];
var pixelWidth = mapWidth;
var pixelHeight = mapHeight;
var bboxWidth = bbox[2] - bbox[0];
var bboxHeight = bbox[3] - bbox[1];
var convertToXY = function(latitude, longitude) {
var widthPct = ( longitude - bbox[0] ) / bboxWidth;
var heightPct = ( latitude - bbox[1] ) / bboxHeight;
var x = Math.floor( pixelWidth * widthPct );
var y = Math.floor( pixelHeight * ( 1 - heightPct ) );
return { x, y };
}