Find out if the user in specific region using GPS data - java

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;
}

Related

Zooming relative to mouse doesnt work in JavaFX

I had a working code, which used ParallelCamera when zooming, and it worked perfectly.
Here's it:
double s = camera.getScaleX();
double d = e.getDeltaY();
double f = 1.2;
double x = e.getSceneX();
double y = e.getSceneY();
if (d < 0) {
f = 2;
} else if (d > 0) {
f = 0.5;
}
double ps = s;
/*if (s * f > 1) {
camera.setScaleX(1);
camera.setScaleY(1);
camera.setScaleZ(1);
camera.setTranslateX(0);
camera.setTranslateY(0);
return;
}
camera.setScaleX(s * f);
camera.setScaleY(s * f);
camera.setScaleZ(s * f);
s = camera.getScaleX();
double dx = ps * (1 - f) * x;
double dy = ps * (1 - f) * y;
System.out.println(dx);
double width = primaryScene.getWidth();
double height = primaryScene.getHeight();
if (camera.getTranslateX() + s * width + dx > width) {
camera.setTranslateX(width - s * width);
} else if ((camera.getTranslateX() + dx < 0)) {
camera.setTranslateX(0);
} else {
camera.setTranslateX(camera.getTranslateX() + dx);
}
if (camera.getTranslateY() + s * height + dy > height) {
camera.setTranslateY(height - s * height);
} else if ((camera.getTranslateY() + dy < 0)) {
camera.setTranslateY(0);
} else {
camera.setTranslateY(camera.getTranslateY() + dy);
}
But I wanted to not have the quality loss, so I rewrote it.
Now it scales the Pane, and not the camera.
I followed exactly the same code, only this time the scaling is reversed.
And when I calculate the position, I first set an offset tomimic my original code.
(The camera's pivot point is at 0,0, but the Pane's is in the center)
The quality loss is gone, but it doesnt really work:
double s = mapLayer.getScaleX();
double d = e.getDeltaY();
double f = 1.2;
double width = primaryScene.getWidth();
double height = primaryScene.getHeight();
double x = (e.getSceneX());
double y = (e.getSceneY());
if (d > 0) {
f = 2;
} else{
f = 0.5;
}
double ps = s;
mapLayer.setScaleX(s * f);
mapLayer.setScaleY(s * f);
mapLayer.setScaleZ(s * f);
s = mapLayer.getScaleX();
double dx = ps * (1 - f) * x;
double dy = ps * (1 - f) * y;
mapLayer.setTranslateX(mapLayer.getTranslateX()+ps*(width/2)*(f-1));
mapLayer.setTranslateY(mapLayer.getTranslateY()+ps*(height/2)*(f-1));
mapLayer.setTranslateX(mapLayer.getTranslateX()+dx);
mapLayer.setTranslateY(mapLayer.getTranslateY()+dy);
I found this, and it worked like a charm:
JavaFX 8 - Zooming Relative to Mouse Pointer. The solution now is this (without clamping):
double delta = 1.2;
double scale = mapLayer.getScaleX(); // currently we only use Y, same value is used for X
double oldScale = scale;
if (e.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale) - 1;
double dx = (e.getSceneX()
- (mapLayer.getBoundsInParent().getWidth() / 2
+ mapLayer.getBoundsInParent().getMinX()));
double dy = (e.getSceneY()
- (mapLayer.getBoundsInParent().getHeight() / 2
+ mapLayer.getBoundsInParent().getMinY()));
mapLayer.setScaleX(scale);
mapLayer.setScaleY(scale);
mapLayer.setScaleZ(scale);
mapLayer.setTranslateX(mapLayer.getTranslateX() - f * dx);
mapLayer.setTranslateY(mapLayer.getTranslateY() - f * dy);

Set player velocity to launch them from point A to point B in an arc with bukkit

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?

How to draw curved polyline in mapbox sdk for Android

I want to draw a curved polyline between two points on a map with the Mapbox SDK.
I could not find any solution from the Mapbox SDK. The Turf library is not ready yet to use it on Android.
I found a solution with the google maps sdk so I converted it to use only the Mapbox sdk :
Inspired by Can I draw a curved dashed line in Google Maps Android?
public static List<LatLng> computeCurvedPolyline(LatLng from, LatLng to, double k) {
//Calculate distance and heading between two points
double distance = from.distanceTo(to);
double heading = computeHeading(from, to);
//Midpoint position
LatLng p = computeOffset(from, distance * 0.5, heading);
//Apply some mathematics to calculate position of the circle center
double x = (1 - k * k) * distance * 0.5 / (2 * k);
double r = (1 + k * k) * distance * 0.5 / (2 * k);
LatLng c = computeOffset(p, x, heading + 90.0);
//Polyline options
List<LatLng> mapboxLatlng = new ArrayList<>();
//Calculate heading between circle center and two points
double h1 = computeHeading(c, from);
double h2 = computeHeading(c, to);
//Calculate positions of points on circle border and add them to polyline options
int numpoints = 100;
double step = (h2 - h1) / numpoints;
for (int i = 0; i < numpoints; i++) {
LatLng pi = computeOffset(c, r, h1 + i * step);
mapboxLatlng.add(pi);
}
return mapboxLatlng;
}
static double computeHeading(LatLng from, LatLng to) {
double fromLat = Math.toRadians(from.getLatitude());
double fromLng = Math.toRadians(from.getLongitude());
double toLat = Math.toRadians(to.getLatitude());
double toLng = Math.toRadians(to.getLongitude());
double dLng = toLng - fromLng;
double heading = Math.atan2(Math.sin(dLng) * Math.cos(toLat), Math.cos(fromLat) * Math.sin(toLat) - Math.sin(fromLat) * Math.cos(toLat) * Math.cos(dLng));
return wrap(Math.toDegrees(heading), -180.0D, 180.0D);
}
static double wrap(double n, double min, double max) {
return n >= min && n < max ? n : mod(n - min, max - min) + min;
}
static double mod(double x, double m) {
return (x % m + m) % m;
}
static LatLng computeOffset(LatLng from, double distance, double heading) {
distance /= 6371009.0D;
heading = Math.toRadians(heading);
double fromLat = Math.toRadians(from.getLatitude());
double fromLng = Math.toRadians(from.getLongitude());
double cosDistance = Math.cos(distance);
double sinDistance = Math.sin(distance);
double sinFromLat = Math.sin(fromLat);
double cosFromLat = Math.cos(fromLat);
double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * Math.cos(heading);
double dLng = Math.atan2(sinDistance * cosFromLat * Math.sin(heading), cosDistance - sinFromLat * sinLat);
return new LatLng(Math.toDegrees(Math.asin(sinLat)), Math.toDegrees(fromLng + dLng));
}
This code will return a list of LatLng points that you can draw on your map, enjoy !
The result produced by this code (not the blurred part of course!):
Any improvement to this code is welcomed !

Convert pixel position from Google static map to LatLng

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;

Convert latitude/longitude point to a pixels (x,y) on mercator projection

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&amp_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 };
}

Categories