I'm trying to generate a point within a radius and I'm getting incorrect values. Someone mind taking a look and telling me what I'm doing wrong for the longitude? This was a formulaic approach posted on a different question...
public static Location generateLocationWithinRadius(Location myCurrentLocation) {
return getLocationInLatLngRad(1000, myCurrentLocation);
}
protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
double x0 = currentLocation.getLatitude();
double y0 = currentLocation.getLongitude();
Random random = new Random();
// Convert radius from meters to degrees
double radiusInDegrees = radiusInMeters / 111000f;
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
double x = w * Math.cos(t);
double y = w * Math.sin(t);
double new_x = x / Math.cos(y0);
double new_y = y / Math.cos(x0);
double foundLatitude;
double foundLongitude;
boolean shouldAddOrSubtractLat = random.nextBoolean();
boolean shouldAddOrSubtractLon = random.nextBoolean();
if (shouldAddOrSubtractLat) {
foundLatitude = new_x + x0;
} else {
foundLatitude = x0 - new_x;
}
if (shouldAddOrSubtractLon) {
foundLongitude = new_y + y0;
} else {
foundLongitude = y0 - new_y;
}
Location copy = new Location(currentLocation);
copy.setLatitude(foundLatitude);
copy.setLongitude(foundLongitude);
return copy;
}
I should also say that for some reason the valid points yield a uniform line of coordinates when looking at them.
I think the latitude is processing correctly whereas the longitude is not.
Your code seems to be more or less based on an idea
which is presented at gis.stackexchange.com
and discussed some more there in this discussion
and in this discussion.
If we take a closer look at it based on those discussions then maybe it makes more sense.
To easily limit the values to a circle it uses the approach of randomizing a direction and a distance. First we get two random double values between 0.0 ... 1.0:
double u = random.nextDouble();
double v = random.nextDouble();
As the radius is given in meters and the calculations require degrees, it's converted:
double radiusInDegrees = radiusInMeters / 111000f;
The degrees vs. meters ratio of the equator is used here. (Wikipedia suggests 111320 m.)
To have a more uniform distribution of the random points the distance is compensated with a square root:
w = r * sqrt(u)
Otherwise there would be a statistical bias in the amount of points near the center vs. far from the center. The square root of 1 is 1 and 0 of course 0, so
multiplying the root of the random double by the intended max. radius always gives a value between 0 and the radius.
Then the other random double is multiplied by 2 * pi because there are 2 * pi radians in a full circle:
t = 2 * Pi * v
We now have an angle somewhere between 0 ... 2 * pi i.e. 0 ... 360 degrees.
Then the random x and y coordinate deltas are calculated with basic trigonometry using the random distance and random angle:
x = w * cos(t)
y = w * sin(t)
The [x,y] then points some random distance w away from the original coordinates towards the direction t.
Then the varying distance between longitude lines is compensated with trigonometry (y0 being the center's y coordinate):
x' = x / cos(y0)
Above y0 needs to be converted to radians if the cos() expects the angle as radians. In Java it does.
It's then suggested that these delta values are added to the original coordinates. The cos and sin are negative for half of the full circle's angles so just adding is fine. Some of the random points will be to the west from Greenwich and and south from the equator. There's no need to randomize
should an addition or subtraction be done.
So the random point would be at (x'+x0, y+y0).
I don't know why your code has:
double new_y = y / Math.cos(x0);
And like said we can ignore shouldAddOrSubtractLat and shouldAddOrSubtractLon.
In my mind x refers to something going from left to right or from west to east. That's how the longitude values grow even though the longitude lines go from south to north. So let's use x as longitude and y as latitude.
So what's left then? Something like:
protected static Location getLocationInLatLngRad(double radiusInMeters, Location currentLocation) {
double x0 = currentLocation.getLongitude();
double y0 = currentLocation.getLatitude();
Random random = new Random();
// Convert radius from meters to degrees.
double radiusInDegrees = radiusInMeters / 111320f;
// Get a random distance and a random angle.
double u = random.nextDouble();
double v = random.nextDouble();
double w = radiusInDegrees * Math.sqrt(u);
double t = 2 * Math.PI * v;
// Get the x and y delta values.
double x = w * Math.cos(t);
double y = w * Math.sin(t);
// Compensate the x value.
double new_x = x / Math.cos(Math.toRadians(y0));
double foundLatitude;
double foundLongitude;
foundLatitude = y0 + y;
foundLongitude = x0 + new_x;
Location copy = new Location(currentLocation);
copy.setLatitude(foundLatitude);
copy.setLongitude(foundLongitude);
return copy;
}
It is hard for me to provide you with a pure Android solution as I never used those API. However I am sure you could easily adapt this solution to generate a random point within a given radius from an existing point.
The problem is solved in a two dimensions space however it is easy to extend to support altitude as well.
Please have a look at the code below. It provides you with a LocationGeneratoras well as my own Location implementation and an unit test proving that it works.
My solution is based on solving the circle equation (x-a)^2 + (y-b)^2 = r^2
package my.test.pkg;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertTrue;
public class LocationGeneratorTest {
private class Location {
double longitude;
double latitude;
public Location(double longitude, double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
}
private class LocationGenerator {
private final Random random = new Random();
Location generateLocationWithinRadius(Location currentLocation, double radius) {
double a = currentLocation.longitude;
double b = currentLocation.latitude;
double r = radius;
// x must be in (a-r, a + r) range
double xMin = a - r;
double xMax = a + r;
double xRange = xMax - xMin;
// get a random x within the range
double x = xMin + random.nextDouble() * xRange;
// circle equation is (y-b)^2 + (x-a)^2 = r^2
// based on the above work out the range for y
double yDelta = Math.sqrt(Math.pow(r, 2) - Math.pow((x - a), 2));
double yMax = b + yDelta;
double yMin = b - yDelta;
double yRange = yMax - yMin;
// Get a random y within its range
double y = yMin + random.nextDouble() * yRange;
// And finally return the location
return new Location(x, y);
}
}
#Test
public void shoulRandomlyGeneratePointWithinRadius () throws Exception {
LocationGenerator locationGenerator = new LocationGenerator();
Location currentLocation = new Location(20., 10.);
double radius = 5.;
for (int i=0; i < 1000000; i++) {
Location randomLocation = locationGenerator.generateLocationWithinRadius(currentLocation, radius);
try {
assertTrue(Math.pow(randomLocation.latitude - currentLocation.latitude, 2) + Math.pow(randomLocation.longitude - currentLocation.longitude, 2) < Math.pow(radius, 2));
} catch (Throwable e) {
System.out.println("i= " + i + ", x=" + randomLocation.longitude + ", y=" + randomLocation.latitude);
throw new Exception(e);
}
}
}
}
NOTE:
This is just a generic solution to obtain a random point inside a circle with the center in (a, b) and a radius of r that can be used to solve your problem and not a straight solution that you can use as such. You most likely will need to adapt it to your use case.
I believe this is a natural solution.
Regards
Kotlin version of Markus Kauppinen answer
fun Location.getRandomLocation(radius: Double): Location {
val x0: Double = longitude
val y0: Double = latitude
// Convert radius from meters to degrees.
// Convert radius from meters to degrees.
val radiusInDegrees: Double = radius / 111320f
// Get a random distance and a random angle.
// Get a random distance and a random angle.
val u = Random.nextDouble()
val v = Random.nextDouble()
val w = radiusInDegrees * sqrt(u)
val t = 2 * Math.PI * v
// Get the x and y delta values.
// Get the x and y delta values.
val x = w * cos(t)
val y = w * sin(t)
// Compensate the x value.
// Compensate the x value.
val newX = x / cos(Math.toRadians(y0))
val foundLatitude: Double
val foundLongitude: Double
foundLatitude = y0 + y
foundLongitude = x0 + newX
val copy = Location(this)
copy.latitude = foundLatitude
copy.longitude = foundLongitude
return copy
}
Longitude and Latitude uses ellipsoidal coordinates so for big radius (hundred meters) the error using this method would become sinificant. A possible trick is to convert to Cartesian coordinates, do the radius randomization and then transform back again to ellipsoidal coordinates for the long-lat. I have tested this up to a couple of kilometers with great success using this java library from ibm. Longer than that might work, but eventually the radius would fall off as the earth shows its spherical nature.
I am almost done of getting the coordinates of a diagonal circle.
Here is what I have so far.
// Center point
double centerX;
double centerY;
double centerZ;
for (double degree = 0D; degree < 360D; degree = degree + 8D) {
double angle = degree * Math.PI / 180D;
// Difference from the center
double x = 1.5D * Math.cos(angle);
double y;
if (degree >= 0D && degree < 90D) {
y = degree / 90D;
} else if (degree >= 90D && degree < 180D) {
y = 1D - ((degree - 90D) / 90D);
} else if (degree >= 180D && degree < 270D) {
y = -1D * ((degree - 180D) / 90D);
} else {
y = -1D * (1D - ((degree - 270D) / 90D));
}
double z = 1.5D * Math.sin(angle);
// New point
double pointX = centerX + x;
double pointY = centerY + y;
double pointZ = centerZ + z;
}
Here is the output in a game.
It is not perfect because it creates some edges and it looks inefficient to me.
How do I correct it?
Is there a better way to do this?
This should look similar to what you have already, but it's simpler and smoother:
double y = 1.0D * Math.sin(angle);
Now, with these dimensions, the result is not quite a circle, but a stretched ellipse. If you want a circle, make sure the coefficients on the cosine and sine obey the Pythagorean Theorem. For example:
double x = 1.5D * Math.cos(angle);
double y = 0.9D * Math.sin(angle);
double z = 1.2D * Math.sin(angle);
These coefficients will ensure that x^2 + y^2 + z^2 is a constant for every angle. You can verify that this is true, given the identity cos^2 + sin^2 = 1. (The coefficient representing the hypotenuse should be attached to the coordinate that uses a different trig function than the other two.)
For the most maintainable code, you might find it better to assign (x, y, z) = (cos, sin, 0) and then apply a rotation matrix, or a sequence of rotation matrices, to the vector (x, y, z). This will be easier to read and harder to mess up, if you want to fine-tune the amount of rotation later.
I wanted to find all points of a circle with angle of 30 degree and radius r, when i am given center of circle (x,y) till we scan entire 360 degree.
Please let me know if i can use any java library related to this.
Code below :
I have written code as below :
<code>
double xpos = xPosition;
double ypos = yPosition;
double step = 2 * Math.PI / 20;
double radius = 50;
List<DevicePositions> listdevicePos = new ArrayList<DevicePositions>();
DevicePositions devicePos = null;
for (double theta = 0; theta < 2 * Math.PI; theta += step) {
double x = xpos + radius * Math.cos(theta);
double y = ypos - radius * Math.sin(theta);
devicePos = new DevicePositions();
devicePos.setxCoordinate(x);
devicePos.setyCoordinate(y);
listdevicePos.add(devicePos);
}
</code>
Thanks
Brijesh
I already know how to find a point on the circumference of a circle based on an angle. The code I'm using to do so is below.
x = Math.sin(Math.toRadians(angle)) * radius;
y = Math.cos(Math.toRadians(angle)) * radius;
I'm trying to undo this process.
So far, I have this code, which only works fully for angles less than or equal to 90 degrees.
DecimalFormat df = new DecimalFormat("###.####");
angleFromX = normalize(
Double.parseDouble(
df.format(
Math.toDegrees(
Math.asin(
(x / radius)
)
)
)
)
);
angleFromY = normalize(
Double.parseDouble(
df.format(
Math.toDegrees(
Math.acos(
(y / radius)
)
)
)
)
);
And here's normalize method used above.
public static double normalize(double angle) {
angle %= 360;
if (angle < 0) {
angle = angle + 360;
}
return angle;
}
You mixed up sin and cos.
double x = Math.cos(Math.toRadians(angle)) * radius;
double y = Math.sin(Math.toRadians(angle)) * radius;
To convert back, use this formula:
double newRadius = Math.hypot(x, y);
double theta = Math.atan2(y,x);
double newAngle = Math.toDegrees(theta);
Based on the implementation, you may need to adjust your value of theta (angle).
If it's in Quadrant 2 or 3, add 180 degrees.
If it's in Quadrant 4, add 360 degrees.
Also you may need to add:
newAngle = (newAngle+360)%360
To keep the angle positive and between 0 and 360.
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 };
}