I have a google static map that I have stored in a 2d array. When the user clicks on a pixel I know the row / col [ 0 - 1024 ].
I need to convert that row col back into a lat lng. I have been trying to use the code below to do the conversions from pixel to lat lng and back.
What I have done is to take the center lat lng and the zoom level.
Convert the center lat lng to pixel using fromLatLngToPoint.
Using the row / col from the image and the width and height of the image get
private static PointF getPointFromRowCol(int row, int col, int width, int height, PointF center) {
double adjustedStartCol = center.x - ((double)width / 2);
double adjustedStartRow = center.y - ((double)height / 2);
double adjustedCol = adjustedStartCol + col;
double adjustedRow = adjustedStartRow + row;
PointF adjustedWorldPoint = new PointF(adjustedCol, adjustedRow);
return GoogleMapsProjection2.fromPointToLatLng(adjustedWorldPoint, 17);
}
The problem is when I put the resulting lat lng back into google maps it is off by 100's of meters.
Any ideas?
public final class GoogleMapsProjection2 {
private final int TILE_SIZE = 256;
private PointF _pixelOrigin;
public double _pixelsPerLonDegree;
public 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(val, valMax);
return res;
}
double degreesToRadians(double deg) {
return deg * (Math.PI / 180);
}
double radiansToDegrees(double rad) {
return rad / (Math.PI / 180);
}
public 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;
}
public 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 PointF fromWorldToPixel(PointF world){
PointF pixelPoint = new PointF(world.x * (1 << 17), world.y * (1 << 17));
return pixelPoint;
}
public static PointF pixelToWorld(PointF world){
PointF pixelPoint = new PointF(world.x / (1 << 17), world.y / (1 << 17));
return pixelPoint;
}
}
This page seems to suggest that you can use the other APIs with the static maps.
It seems worth mentioning that you can actually have the Google Maps Javascript 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.
You can try this Java function:
function convertPixelToGeo(tx:Number, ty)
{
$LatBottomSin=min(max(sin($this->mapLatBottom*(M_PI/180)),-0.9999),0.9999);
$worldMapWidth=(($this->mapWidth/$mapLonDelta)*360)/(2*M_PI);
$worldMapRadius = $mapWidth / $mapLonDelta * 360/(2*M_PI);
$mapOffsetY = ($worldMapRadius/2 *log((1+sin($LatBottomSin))/(1-sin($LatBottomSin))));
$equatorY = $this->mapHeight + mapOffsetY;
$a = ($equatorY-$ty)/$worldMapRadius;
$lat = 180/Math.PI * (2 * Math.atan(Math.exp($a)) - Math.PI/2);
$long = $this->mapLonLeft+$tx/$mapWidth*$mapLonDelta;
return new Point($lat,$long);
}
If it doesn't work try this:
int numTiles = 1 << zoom;
tx = tx / numTiles;
ty = ty / numTiles;
Related
I'm trying to code a plugin for a server and I want to launch a player from point A to point B.
I've tried this but a lot of the time it is inaccurate and misses the block by a few blocks.
private Vector calculateVelocity(Location fromLoc, Location toLoc, int heightGain) {
Vector from = fromLoc.toVector();
Vector to = toLoc.toVector();
//Block locations
int endGain = to.getBlockY() - from.getBlockY();
double horizDist = fromLoc.distance(toLoc);
//Height gain
int gain = heightGain;
double maxGain = (gain > (endGain + gain) ? gain : (endGain + gain));
//Solve quadratic equation for velocity
double a = -horizDist * horizDist / (4 * maxGain);
double b = horizDist;
double c = -endGain;
double slope = -b / (2 * a) - Math.sqrt(b * b - 4 * a * c) / (2 * a);
//Vertical velocity
double veloY = Math.sqrt(maxGain * 0.115);
//Horizontal velocity
double vH = veloY / slope;
//Calculate horizontal direction
int distX = to.getBlockX() - from.getBlockX();
int distZ = to.getBlockZ() - from.getBlockZ();
double mag = Math.sqrt(distX * distX + distZ * distZ);
double dirX = distX / mag;
double dirZ = distZ / mag;
//Horizontal velocity components
double veloX = vH * dirX;
double veloZ = vH * dirZ;
//Actual velocity
Vector velocity = new Vector(veloX, veloY, veloZ);
return velocity;
}
Any suggetions on what I should do to get it accurate 100% of the time?
I want to find out if the user in specific region using GPS data and consider with accuracy info to reduce error, because the program will prompt an alert if user definitely out of the region.
GPS sensor return latitude, longitude and accuracy(in meter), I can draw a circle using those data:
135.500908,34.661964,30.0
There're array of coordinates in sequence represent the specific region:
135.500350,34.667011
135.506101,34.666853
135.505972,34.663076
135.505135,34.663111
135.504942,34.662387
135.504084,34.662440
135.504062,34.663146
135.502968,34.663217
135.502689,34.663764
135.502431,34.664205
135.502110,34.664646
135.501680,34.665105
135.501509,34.665246
135.500844,34.665229
135.500371,34.665511
My idea is to find out if any collisions of the polygon with the circle by using line-circle collisions detection algorithm, but it looks something wrong in my code, and seems I can't directly use that info due to radius/degree, could anyone help me out? Or let me know if any more simple solution?
public static boolean possiblyInside(List<Double> arrayX, List<Double> arrayY, double locationX, double locationY, double locationAccuracy) {
if (arrayX.size() != arrayY.size()) {
throw new IllegalArgumentException("Array length not equal");
}
boolean anyCircleLineIntersection = false;
if (arrayX.size() > 1) {
for (int i = 0; i < arrayX.size(); i++) {
double p1x = i == 0 ? arrayX.get(arrayX.size() - 1) : arrayX.get(i - 1);
double p1y = i == 0 ? arrayY.get(arrayY.size() - 1) : arrayY.get(i - 1);
double p2x = arrayX.get(i);
double p2y = arrayY.get(i);
if (circleLineIntersection(p1x, p1y, p2x, p2y, locationX, locationY, locationAccuracy)) {
anyCircleLineIntersection = true;
break;
}
}
}
return anyCircleLineIntersection;
}
private static boolean circleLineIntersection(double p1X, double p1Y, double p2X, double p2Y, double centerX, double centerY, double locationAccuracy) {
double rad = (180 / Math.PI);
double r = (locationAccuracy / 1000);
p1X = p1X * rad;
p1Y = p1Y * rad;
p2X = p2X * rad;
p2Y = p2Y * rad;
centerX = centerX * rad;
centerY = centerY * rad;
// Transform to local coordinates
double localP1X = p1X - centerX;
double localP1Y = p1Y - centerY;
double localP2X = p2X - centerX;
double localP2Y = p2Y - centerY;
// Pre-calculate this value. We use it often
double pDiffX = localP2X - localP1X;
double pDiffY = localP2Y - localP1Y;
double a = (pDiffX) * (pDiffX) + (pDiffY) * (pDiffY);
double b = 2 * ((pDiffX * localP1X) + (pDiffY * localP1Y));
double c = (localP1X * localP1X) + (localP1Y * localP1Y) - (r * r);
double delta = b * b - (4 * a * c);
return delta >= 0.0;
}
There is a method called Geofencing. Google already provides such functionality for you. And you don't have to deal with all these complex calculations.
You can fire events when the user entered a specific area / exited a specific area / after staying for some time in a specific area. Or you can make different combinations.
Here is an article of how you can use Geofencing. It consists of 4 separated articles.
Thank you for Todor Kostov's answer.
I know Android has provided Geofencing API, but it is not a perfect fit for my situation due to its implementation and limitations, and I would like to sync the algorithm with iOS version app as well. (Even I know the algorithm are not good as iOS or Android provided, and it also looks a bit silly).
Finally, I solved the problem in this way:
Ensure current location not inside the polygon (use point-in-polygon algorithm)
Loop through all line segment of the region polygon, find out the
closest coordinate(PointA) to the current location(PointB)
Calculate the distance between PointA and PointB, convert it to meter(X)
If X > location accuracy (also in meter), the user is definitely out of the particular region
p.s. I'm not good at math and geolocation, point out if any incorrect
public static boolean possiblyInside(List<Double> arrayX, List<Double> arrayY, double locationX, double locationY, double locationAccuracy) {
if (arrayX.size() != arrayY.size()) {
throw new IllegalArgumentException("Array length not equal");
}
if (arrayX.size() < 3) {
return false;
}
double minimumDistance = Double.MAX_VALUE;
for (int i = 0; i < arrayX.size(); i++) {
double p1x = i == 0 ? arrayX.get(arrayX.size() - 1) : arrayX.get(i - 1);
double p1y = i == 0 ? arrayY.get(arrayY.size() - 1) : arrayY.get(i - 1);
double p2x = arrayX.get(i);
double p2y = arrayY.get(i);
Coordinate closest = getClosestPointOnLine(p1x, p1y, p2x, p2y, locationX, locationY);
double currentDistance = distanceMeterBetweenPoints(closest.latitude, closest.longitude, locationX, locationY);
if (currentDistance < minimumDistance) {
minimumDistance = currentDistance;
}
}
return (minimumDistance <= locationAccuracy);
}
private static Coordinate getClosestPointOnLine(double sx1, double sy1, double sx2, double sy2, double px, double py) {
double xDelta = sx2 - sx1;
double yDelta = sy2 - sy1;
if ((xDelta == 0) && (yDelta == 0)) {
throw new IllegalArgumentException("Line start equals line end");
}
double u = ((px - sx1) * xDelta + (py - sy1) * yDelta) / (xDelta * xDelta + yDelta * yDelta);
final Coordinate closestPoint;
if (u < 0.0) {
closestPoint = new Coordinate(sx1, sy1);
} else if (u > 1.0) {
closestPoint = new Coordinate(sx2, sy2);
} else {
closestPoint = new Coordinate((int) Math.round(sx1 + u * xDelta), (int) Math.round(sy1 + u * yDelta));
}
return closestPoint;
}
public static double distanceMeterBetweenPoints(double aX, double aY, double bX, double bY) {
double rad = Math.PI / 180;
int r = 6371;
double dLat = (aX - bX) * rad;
double dLng = (aY - bY) * rad;
double x = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(aX * rad) * Math.cos(bX * rad) * Math.pow(Math.sin(dLng / 2), 2);
double y = 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x));
return r * y * 1000;
}
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 am using the CanvasTileProvider in Google Maps Android v2.
I can convert lat long points to screen pixels.
However I would like to create a method to convert a distance to screen pixels. This will allow me to draw a circle of x radius. Can anyone help with this?
The code below I have butchered and modified from somewhere else so credit to the original author.
/**
* Converts between LatLng coordinates and the pixels inside a tile.
*/
public class TileProjection {
public int x;
public int y;
private int zoom;
private int TILE_SIZE;
private DoublePoint pixelOrigin_;
private double pixelsPerLonDegree_;
private double pixelsPerLonRadian_;
TileProjection(int tileSize, int x, int y, int zoom) {
this.TILE_SIZE = tileSize;
this.x = x;
this.y = y;
this.zoom = zoom;
pixelOrigin_ = new DoublePoint(TILE_SIZE / 2, TILE_SIZE / 2);
pixelsPerLonDegree_ = TILE_SIZE / 360d;
pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}
/**
* Get the dimensions of the Tile in LatLng coordinates
*/
public LatLngBounds getTileBounds() {
DoublePoint tileSW = new DoublePoint(x * TILE_SIZE, (y + 1) * TILE_SIZE);
DoublePoint worldSW = pixelToWorldCoordinates(tileSW);
LatLng SW = worldCoordToLatLng(worldSW);
DoublePoint tileNE = new DoublePoint((x + 1) * TILE_SIZE, y * TILE_SIZE);
DoublePoint worldNE = pixelToWorldCoordinates(tileNE);
LatLng NE = worldCoordToLatLng(worldNE);
return new LatLngBounds(SW, NE);
}
/**
* Calculate the pixel coordinates inside a tile, relative to the left upper
* corner (origin) of the tile.
*/
public PointF latLngToPoint(LatLng latLng) {
DoublePoint result = new DoublePoint(1, 1);
// Log.d("Aero","x " + String.valueOf(x));
// Log.d("Aero","y " + String.valueOf(y));
latLngToWorldCoordinates(latLng, result);
worldToPixelCoordinates(result, result);
result.x -= x * TILE_SIZE;
int numTiles = 1 << zoom;
if (latLng.longitude < 0) {
result.x = result.x + (numTiles * TILE_SIZE);
}
result.y -= y * TILE_SIZE;
return new PointF((float) result.x, (float) result.y);
}
private DoublePoint pixelToWorldCoordinates(DoublePoint pixelCoord) {
int numTiles = 1 << zoom;
DoublePoint worldCoordinate = new DoublePoint(pixelCoord.x / numTiles,
pixelCoord.y / numTiles);
return worldCoordinate;
}
/**
* Transform the world coordinates into pixel-coordinates relative to the
* whole tile-area. (i.e. the coordinate system that spans all tiles.)
* <p/>
* <p/>
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void worldToPixelCoordinates(DoublePoint worldCoord, DoublePoint result) {
int numTiles = 1 << zoom;
result.x = worldCoord.x * numTiles;
result.y = worldCoord.y * numTiles;
}
private LatLng worldCoordToLatLng(DoublePoint worldCoordinate) {
DoublePoint origin = pixelOrigin_;
double lng = (worldCoordinate.x - origin.x) / pixelsPerLonDegree_;
double latRadians = (worldCoordinate.y - origin.y)
/ -pixelsPerLonRadian_;
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians))
- Math.PI / 2);
return new LatLng(lat, lng);
}
/**
* Get the coordinates in a system describing the whole globe in a
* coordinate range from 0 to TILE_SIZE (type double).
* <p/>
* Takes the resulting point as parameter, to avoid creation of new objects.
*/
private void latLngToWorldCoordinates(LatLng latLng, DoublePoint result) {
DoublePoint origin = pixelOrigin_;
result.x = origin.x + latLng.longitude * 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(Math.toRadians(latLng.latitude)), -0.9999,
0.9999);
result.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny))
* -pixelsPerLonRadian_;
}
;
/**
* Return value reduced to min and max if outside one of these bounds.
*/
private double bound(double value, double min, double max) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;
}
/**
* A Point in an x/y coordinate system with coordinates of type double
*/
public static class DoublePoint {
double x;
double y;
public DoublePoint(double x, double y) {
this.x = x;
this.y = y;
}
}
}
This is what I am proposing to use:
public Double MetersToPixels(LatLng latLng, Double distance){
double tileScale = TILE_SIZE / 256;
double pixelsPerMeter =1 / (156543.03392 * Math.cos(latLng.latitude * Math.PI / 180) / Math.pow(2, zoom)) * tileScale;
return pixelsPerMeter * distance;
}
At first you should be aware of the fact, that a circle on the surface of the earth is not exactly a circle on the map. But if you ignore this inaccuracy, you just need to create a LatLng point in 25nm distance, and then use latLngToPoint method to get the pixels. Comparing them with the pixels of the center, gives you the radius. For creating a LatLng in a given distance see the answer to this SO question (method move)
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 };
}