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.
is supposed to calculate the coordinates of a projectile launched with respect to time (steps of 100ms), with a linear equation, and it outputs linear numbers, but if i plot this equation with CalcMe.com (math tool) it makes a parabolic plot
InVel = Double.parseDouble(jTextField1.getText());
g = Double.parseDouble(jTextField8.getText());
y = 1;
while(y >= -1) {
t += 100;
x = InVel * TimeUnit.MILLISECONDS.toSeconds(t) * Math.cos(45);
y = InVel * TimeUnit.MILLISECONDS.toSeconds(t) * Math.sin(45) - (1 / 2) * g * Math.pow(TimeUnit.MILLISECONDS.toSeconds(t), 2);
//System.out.print(Double.toString(x));
//System.out.printf(" ");
System.out.print(Double.toString(y));
System.out.printf("%n");
}
jTextField6.setText(Double.toString(x));
the code is in java
g is constant (9.8)
and invel is given by user so its constant too
g is the gravity and invel the initial velocity of the projectile
the equation is:x=invel*time*cos(45) and y=invel*time*sin(45)-(1/2)*g*t^2
anyone can help me?
Your milisecond to second value conversion method TimeUnit.MILLISECONDS.toSeconds(t) is the main fact. Its returning long value which one you are wanted double. Please take a look on below code. Probably its your answer. Just replace hard-coded value with your jTextField
public static void main(String[] args) {
double InVel = Double.parseDouble("10.555");
double g = Double.parseDouble("9.8");
double y = 1;
double x=0;
double t=0;
while(y >= -1) {
t += 100;
double timeInSeconds = (t / (double)1000) % (double)60;
x = InVel * timeInSeconds * Math.cos(45);
y = InVel * timeInSeconds * Math.sin(45) - ((double) 1 / (double) 2) * g * Math.pow(timeInSeconds, 2);
//System.out.print(Double.toString(x));
//System.out.printf(" ");
System.out.println("X = " + x + " Y = " + Double.toString(y));
System.out.printf("%n");
}
}
I have this code to draw some vertex and edges, and
I have tried almost all the possibilities that were within my reach, but I believe the bug is in the method rotate or in the construtor
but I'm not sure
public static int CONFIG_NODE_DIAMETER = 20; //pixels
//construtor
public GraphDraw(Graph<V, E> graph) {
//count of vertex
int N = graph.numVertex();
double width = this.getWidth();
double height = this.getHeight();
Point2D center = new Point2D(width / 2, height / 2);
double angleIncrement = 360f / N;
//get all vertex from graph
ArrayList<Vertex<V>> vertex = graph.getVertex();
//draw the line and point
boolean first = true;
Point2D p = null;
for (Vertex<V> v : vertex ) { {
if (first) {
if (width > height) {
p = new Point2D(center.getX(),
center.getY() - height / 2 + CONFIG_NODE_DIAMETER * 2);
} else {
p = new Point2D(center.getX(),
center.getY() - width / 2 + CONFIG_NODE_DIAMETER * 2);
}
first = false;
} else {
p = rotate(p, center, angleIncrement);
}
}
}
}
the method that makes the rotation between 2 points
private static Point2D rotate(Point2D point, Point2D pivot, double angle_degrees) {
double angle = Math.toRadians(angle_degrees);
double sin = Math.sin(angle);
double cos = Math.cos(angle);
//translate to origin
Point2D result = point.subtract(pivot);
// rotate point
Point2D rotatedOrigin = new Point2D(
result.getX() * cos - result.getY() * sin,
result.getX() * sin + result.getY() * cos);
// translate point back
result = rotatedOrigin.add(pivot);
return result;
}
I wanna do like the image below and I tried to rotate but it is not working
any suggestion?
in this link, you can check all method in class GraphDraw, and I dont put because the post would be extensive
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 };
}