Getting Layer Feature Information from GeoServer (Google Maps) - java

I am writing an Android App with Android Studio (using Java). The app is using Google Maps and has a layer with field ownership information that it's getting from a geoserver. The code to set this up is as follows, and is working well.
public class App1Step1 extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_app1_step1);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
TileProvider tileProvider = TileProviderFactory.getTileProvider();
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(tileProvider));
}
}
class TileProviderFactory {
private static final String GEOSERVER_FORMAT =
"http://xxxxxxxx.at/geoserver/wms" +
"?service=WMS" +
"&version=1.1.1" +
"&request=GetMap" +
"&layers=satgrass:INSPIRE_SCHLAEGE_2020_POLYGON" +
"&bbox=%f,%f,%f,%f" +
"&width=256" +
"&height=256" +
"&srs=EPSG:900913" +
"&format=image/png" +
"&transparent=true";
static TileProvider getTileProvider() {
TileProvider tileProvider = new WMSTileProvider(256,256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, GEOSERVER_FORMAT, bbox[MINX], bbox[MINY], bbox[MAXX], bbox[MAXY]);
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
};
return tileProvider;
}
}
abstract class WMSTileProvider extends UrlTileProvider {
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = {-20037508.34789244, 20037508.34789244};
//array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
// Construct with tile size in pixels, normally 256, see parent class.
public WMSTileProvider(int x, int y) {
super(x, y);
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x+1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y+1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
The code above will produce the following overlay on Google Maps. On the geoserver, the layer is saved as EPSG:31287 (Austria Lambert).
What I would like to do now is to get the feature information, when I click on one of the fields. I tried around a lot, but am unable to figure out how to calculate the necessary values (width, height, x, y, bbox) to get the feature information from the latitude and longitude I clicked at. To be specific, these are the values from the query that I'm missing.
String url = "http://xxxxxxxxxxx.at/geoserver/wms" +
"?service=WMS" +
"&version=1.1.1" +
"&request=GetFeatureInfo" +
"&layers=satgrass:INSPIRE_SCHLAEGE_2020_POLYGON" +
"&query_layers=satgrass:INSPIRE_SCHLAEGE_2020_POLYGON" +
"&exceptions=application/vnd.ogc.se_inimage" +
"&x=" + // ????????
"&y=" + // ????????
"&bbox=" + // ????????
"&width=" + // ????????
"&height=" + // ????????
"&srs=EPSG:900913" + // EPSG:900913 or EPSG:31287 ?
"&format=image/png" +
"&info_format=application/json" +
"&transparent=true" +
"&feature_count=50";
What I got done so far (I think) is to calculate the x and y position of where I clicked from the latitute, longitude and the zoom level.
private void getFeatureInfo(LatLng latLng) {
// get current zoom
int zoom = (int)mMap.getCameraPosition().zoom;
// get "click" point coordinates in pixels
long pointNorthWestX = lonToX(latLng.longitude, zoom);
long pointNorthWestY = latToY(latLng.latitude, zoom);
}
public static long lonToX(double lon, int zoom) {
int offset = 256 << (zoom - 1);
return (int)Math.floor(offset + (offset * lon / 180));
}
public static long latToY(double lat, int zoom) {
int offset = 256 << (zoom - 1);
return (int)Math.floor(offset - offset / Math.PI * Math.log((1 + Math.sin(Math.toRadians(lat))) / (1 - Math.sin(Math.toRadians(lat)))) / 2);
}
Unfortunately, these values seem to be way off in comparison of what I get when I click the same position in the layer preview on geoserver.
Does anyone know what the necessary calculation steps are to get the feature information at the position I clicked at from the latitude, longitude and zoom level? I'd be really thankful for any help with this.

A getFeatureRequest is, essentially, the getMap request you used to fetch the map you are querying with some parameters added. You must add QUERY_LAYERS which is the names of the layers you want information about, and X & Y (or I & J in version 1.3.0) which are the location of the pixel you want information about (so where you clicked). Optionally, you can add an info_format to control the returned format.
For further details I suggest you read the standard document.

I have found the solution for this and it was way easier than I thought. The geoserver already supports projection transformations from the LatLon value that I get from clicking on the map to the XY-Coordinates that are needed for the getFeatureInfo() request.
I have change the code to the following.
private void getFeatureInfo(LatLng latLng) {
String url = "http://xxxxxxxxxx.at/geoserver/wms" +
"?service=WMS" +
"&version=1.1.1" +
"&request=GetFeatureInfo" +
"&layers=satgrass:INSPIRE_SCHLAEGE_2020_POLYGON" +
"&query_layers=satgrass:INSPIRE_SCHLAEGE_2020_POLYGON" +
"&exceptions=application/vnd.ogc.se_inimage" +
"&x=128" +
"&y=128" +
"&bbox=" + (latLng.longitude - 0.0000000001) + "," + (latLng.latitude - 0.0000000001) + "," + (latLng.longitude + 0.0000000001) + "," + (latLng.latitude + 0.0000000001) +
"&width=256" +
"&height=256" +
"&srs=EPSG:4326" +
"&format=image/png" +
"&info_format=application/json" +
"&transparent=true" +
"&feature_count=50";
}
The width and height are both 256, which I have initially set in the TileProvider. The x and y values are both 128, which would be the center of each tile.
What I basically needed to do now is to create a bounding box in which the geoserver will look for features. I made that as small as possible (should be in cm or mm range) so only ever 1 feature is returned. For that, I'm distracting 0.0000000001 from the minX and minY values and adding 0.0000000001 to the maxX and maxY values.
The important part was to use EPSG:4326 for this request. The geoserver will then automatically calculate the projection for the EPSG:31287 layer and return the feature information at that point.

Related

Eccentricity Vector in Java not accurate as orbit moves along

I am writing a small libgdx program in java that was inspired by Kerbal Space Program and I am now writing the class that controls objects that will have forces acted on them. Each of these objects have a velocity vector that is changed by a forces vector.
The program runs around 60 frames per second and every frame the calculations done on the force to change velocity are done 1000 times. ( I have played with this number a lot however). Right now the program is incredibly simple and the only force that is exerted and calculated every iteration is from the planet.
forcedub = ((gravConstanat*parentBody.getMass())/Math.pow(loc.distance(parentBody.getLoc()),2));
force = new Point2D(((-1)*forcedub*Math.cos(a)),((-1)*forcedub*Math.sin(a)));
This changes the velocity sightly, the position is adjusted and the loop continues. The process works very well and seems stable. I haven't run it for days on end, but the orbit even at relatively high eccentricities seems stable. UNFORTUNATELY I need to be able to speed this process up so it doesn't take 2 days real time to get to the moon. So I needed a system that puts the orbit "on rails" and doesn't need to recalculate the forces each iterations. Once the multiplier value there gets set too high the orbit falls apart.
Good news is I already have this system in place, I just can't switch between the two.
I need a few values from the orbit to do this in my system. (I know some values are a bit redundant but it is what it is).
The semi major axis
the semi minor axis
the eccentricity vector
eccentricity
true anomaly
focus information.
To cut to the chase, the biggest issue is the eccentricity vector / eccentricity. My 'bake' function is the one that attempts to compute the values from the state vectors of the orbit every iteration and the value of the eccentricity varies drastically where it should stay the same in a standard orbit.The direction the vector is all over the place as well.
I have hard coded a single object that should have an eccentricity of about .62 and the eccentricity vector should point to pi, but the value wanders between .25 and .88 and the direction wanders between pi and pi / 3 ish.
Here are two versions of how to get the eccvecc from the state vectors, and I have tried them both. They both give the exact same results:
https://space.stackexchange.com/questions/37331/why-does-the-eccentricity-vector-equation-always-equal-1
https://en.wikipedia.org/wiki/Eccentricity_vector
public void bake(){
double velSq = Math.pow(vel.distance(0,0),2);
double r = loc.distance(parentBody.getLoc());
double gm = gravConstanat*parentBody.getMass();
Point2D posr = new Point2D(loc.getX()-parentBody.getX(), loc.getY()-parentBody.getY());
Point2D calc1 = posr.scale((velSq/gm));
Point2D calc2 = vel.scale((dotProd(posr,vel)/gm));
Point2D calc3 = posr.scale(1/r);
Point2D eccVecc = (calc1.minus(calc2)).minus(calc3);
ecc = eccVecc.distance(0,0);
w = Math.toRadians(90)-(Math.atan2(eccVecc.x(),eccVecc.y()));
semiA = (gm*r)/(2*gm - r*velSq);
semiB = semiA*(Math.sqrt((1-Math.pow(ecc,2))));
focus = findFocus(semiA,semiB);
System.out.println("ecc " + ecc + " W " + w + " SEMI A " + semiA);
System.out.println();
}
Here is the entire class:
**initial distance is about 900,000 to the left of the parent body
**parent mass is 5.3*Math.pow(10,22)
public class Klobject {
String name;
TextureAtlas textureAtlas;
Sprite sprite;
Cbody parentBody;
Point2D loc;
Point2D vel;
public boolean acceleration;
double MULTIPLIER;
static double gravConstanat = 6.67*Math.pow(10,-11);
double semiA, semiB, ecc, w;
protected double t;
protected double mass;
protected double rotateRate;
protected double focus;
public Klobject(Cbody cb){
mass = 1;
MULTIPLIER = 1;
rotateRate = 0;
parentBody = cb;
acceleration = false;
cb.addKlob(this);
loc = new Point2D((parentBody.getX() - 900_000f ),
(parentBody.getY()));
vel = new Point2D(0,2526.733);
sprite = textureAtlas.createSprite(name);
sprite.setOrigin(sprite.getWidth()/2, sprite.getHeight()/2);
bake();
}
public void update(float dt){
oneXupdate(dt);
bake();
}
private void oneXupdate(float dt){
int timesLooped = 1000;
double a;
double forcedub;
Point2D force;
Point2D velout;
double dx;
double dy;
dt = dt/timesLooped;
for (int i = 0; i < timesLooped; i++){
velout = vel.scale(MULTIPLIER);
dx = (dt*velout.getX());
dy = (dt*velout.getY());
loc = new Point2D(loc.getX()+dx, loc.getY()+dy);
a = Math.atan2(loc.getX()-parentBody.getX(), loc.getY()-parentBody.getY());
a = Math.toRadians(90)-a;
forcedub = ((gravConstanat*parentBody.getMass())/Math.pow(loc.distance(parentBody.getLoc()),2));
force = new Point2D(((-1)*forcedub*Math.cos(a)),((-1)*forcedub*Math.sin(a)));
force = force.scale(MULTIPLIER*MULTIPLIER);
velout = velout.plus(new Point2D(force.getX()*dt,force.getY()*dt));
vel = velout.scale(1/MULTIPLIER);
}
}
public void bake(){
double velSq = Math.pow(vel.distance(0,0),2);
double r = loc.distance(parentBody.getLoc());
double gm = gravConstanat*parentBody.getMass();
Point2D posr = new Point2D(loc.getX()-parentBody.getX(), loc.getY()-parentBody.getY());
Point2D calc1 = posr.scale((velSq/gm));
Point2D calc2 = vel.scale((dotProd(posr,vel)/gm));
Point2D calc3 = posr.scale(1/r);
Point2D eccVecc = (calc1.minus(calc2)).minus(calc3);
ecc = eccVecc.distance(0,0);
w = Math.toRadians(90)-(Math.atan2(eccVecc.x(),eccVecc.y()));
semiA = (gm*r)/(2*gm - r*velSq);
semiB = semiA*(Math.sqrt((1-Math.pow(ecc,2))));
focus = findFocus(semiA,semiB);
System.out.println("ecc " + ecc + " W " + w + " SEMI A " + semiA);
System.out.println();
}
public double findFocus(double a, double b){
return Math.sqrt(a*a - b*b);
}
public double getX(){
return loc.getX();
}
public double getY(){
return loc.getY();
}
public void setRotateRate(double rr){rotateRate = rr;}
public String getName(){
return name;
}
public Sprite getSprite(){
return sprite;
}
public void setMultiplier(double mult){
MULTIPLIER = mult;
}
public Point2D getLoc(){
return loc;
}
public void setLoc(Point2D newLoc){
loc = newLoc;
}
public double dotProd(Point2D a, Point2D b){
return a.x()*b.x() + a.y()+b.y();
}
}

change radius by zoom level

I wish to calculate radius in meters or km according to the zoom level.
I've found this formula - which gives me the roughly calculated meters per pixel:
https://groups.google.com/forum/#!topic/google-maps-js-api-v3/hDRO4oHVSeM
Also, these links assisted me in understanding what I was trying to accomplish exactly:
google map API zoom range
How to get center of map for v2 android maps?
and this is the implementation:
double calculateScale(float zoomLevel, LatLng centerPos){
double meters_per_pixel = 156543.03392 * Math.cos(centerPos.latitude * Math.PI / 180) / Math.pow(2, zoomLevel);
return meters_per_pixel;
}
and this is how I listen to the zoom level changing:
_map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
Log.d(Consts.TAGS.FRAG_MAIN_MAP,"Current Zoom : " + _map.getCameraPosition().zoom);
Log.d(Consts.TAGS.FRAG_MAIN_MAP,"Center Lat : " + _map.getCameraPosition().target.latitude +
", Center Long : " + _map.getCameraPosition().target.longitude);
}}
Now, whenever the user changes zoom level, I wish to determine what is the maximum radius that can be displayed within the map view...
You can use next snippet:
_map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
#Override
public void onCameraIdle() {
Log.d(Consts.TAGS.FRAG_MAIN_MAP,"Current Zoom : " + _map.getCameraPosition().zoom);
Log.d(Consts.TAGS.FRAG_MAIN_MAP,"Center Lat : " + _map.getCameraPosition().target.latitude +
", Center Long : " + _map.getCameraPosition().target.longitude);
float zoom = _map.getCameraPosition().zoom;
LatLng position = _map.getCameraPosition().target;
double maxRadius = calculateScale(zoom, position) * mapViewDiagonal() / 2;
}
}
private mapViewDiagonal() {
return Math.sqrt(_map.getWidth() * _map.getWidth() + _map.getHeight() * _map.getHeight());
}

Convert longitude and latitude coordinates to image of a map pixels X and Y coordinates Java

I have a map of Israel.
I need to create a function that gets two double parameters (longitude and latitude) and that function should draw a small circle on that area in the map image.
I have the following info about the map:
The width of the map in pixels
The height of the map in pixels
At-least one coordinate in latitude/longitude location on the map
Distance in pixels between each degree
I need to convert the coordinates I get to pixel X, Y based on that image.
There's the following map image:
For example (Not an accurate example, it is just an example so you understand what I mean), the top-left coordinate is 33.5, 34 and the X, Y of it is 0,0 on the map.
How can I convert these coordinates to X, Y coordinates?
I tried this answer but it didn't really work, it shows me at 31.5, 34.5 instead of at 33, 34.
UPDATE: here's a dummy quick code example of the other question;
public class MapRenderer extends JFrame {
public static void main(String... args) throws IOException {
new MapRenderer();
}
public MapRenderer() throws IOException {
setSize(new Dimension(614, 1141));
add(new TestPane());
setVisible(true);
}
}
class TestPane extends JPanel {
private BufferedImage image;
public TestPane() throws IOException {
File file = new File("israel_map.jpg");
BufferedImage image = ImageIO.read(file);
this.image = image;
}
#Override
public void paintComponent(Graphics g) {
double lon = 34;
double lat = 33;
int mapW = 614;
int mapH = 1141;
double x = (lon + 180) * (mapW / 360);
double latRad = lat * Math.PI / 180;
double mercN = Math.log( Math.tan( (Math.PI / 4) + (latRad / 2)) );
double y = (mapH / 2) - (mapW * mercN / (2 * Math.PI));
System.out.println("[lon: " + lon + " lat: " + lat + "]: X: " + x + " Y: " + y);
g.drawImage(image, 0, 0, null);
g.setColor(Color.RED);
g.drawOval((int) x, (int) y, 5, 5);
}
}
Output:
[lon: 34.0 lat: 33.0]: X: 214.0 Y: 510.3190109117399
screenshot:
https://gyazo.com/5a19dece37ebace496c6b8d68eb9ec3c
You need to add offsets and lengths of the map in longitude/latitude in addition to pixels. Then you can just do a conversion.
static final int mapWidth = 614, mapHeight = 1141;
// offsets
static final double mapLongitudeStart = 33.5, mapLatitudeStart = 33.5;
// length of map in long/lat
static final double mapLongitude = 36.5-mapLongitudeStart,
// invert because it decreases as you go down
mapLatitude = mapLatitudeStart-29.5;
private static Point getPositionOnScreen(double longitude, double latitude){
// use offsets
longitude -= mapLongitudeStart;
// do inverse because the latitude increases as we go up but the y decreases as we go up.
// if we didn't do the inverse then all the y values would be negative.
latitude = mapLatitudeStart-latitude;
// set x & y using conversion
int x = (int) (mapWidth*(longitude/mapLongitude));
int y = (int) (mapHeight*(latitude/mapLatitude));
return new Point(x, y);
}
public static void main(String[] args) {
System.out.println(getPositionOnScreen(33.5, 33.5).toString());
System.out.println(getPositionOnScreen(35, 32).toString());
System.out.println(getPositionOnScreen(36.5, 29.5).toString());
}
This will print out the following:
java.awt.Point[x=0,y=0]
java.awt.Point[x=307,y=427]
java.awt.Point[x=614,y=1141]
Your code does not take into account that your map is not the entire world map. You need to adjust it so that it has an offset (because the top left of the map is not 0,0 so to speak) and so that it doesn't think the width of the map is 360' and the height 180'.
For starters x = (longitude+180)*(mapWidth/360) should be more like x = (longitude+<distance west of prime meridian of left side of map>)*(mapWidth/<width of map in degrees>)

Conversion of Latitude/Longitude into image coordinates (Pixel coordinates) on a simple cylindrical projection

I have taken a image from Google earth, whose latitude/longitude of all the 4 corners are known. I am capturing latitude/longitudes using a GPS sensor. I have to convert these captured latitude/longitudes to image coordinates(pixel coordinates) using java. I will use the image coordinates to simulate as if a vehicle is moving on a static map( image taken from Google Earth).
I found this formula and tried to implement it
Determine the left-most longitude in your 1653x1012 image (X)
Determine the east-most longitude in your 1653x1012 image (Y)
Determine Longitude-Diff (Z = Y - X)
Determine north-most latitude in your 1653x1012 image (A)
Determine south-most latitude in your 1653x1012 image (B)
Determine Latitude-Diff (C = A - B)
Given a Latitude and Longitude, to determine which pixel they clicked on:
J = Input Longitude
K = Input Latitude
Calculate X-pixel
XPixel = CInt(((Y - J) / CDbl(Z)) * 1653)
Calculate Y-pixel
YPixel = CInt(((A - K) / CDbl(C)) * 1012)
This is the code I used.
import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
public class LatLongService {
private static LatLongService latLangService;
private BufferedReader reader = null;
private String st;
private LatLongService() {
try {
reader = new BufferedReader(new FileReader(new File(
"resources/GPS_lat_long_2.txt")));
} catch (Exception e) {
e.printStackTrace();
}
}
public static LatLongService getInstance() {
if (latLangService == null)
latLangService = new LatLongService();
return latLangService;
}
public List<Point2D> readLatLongList() {
List<Point2D> pointList = new ArrayList<Point2D>();
StringBuffer xStr;
StringBuffer yStr = new StringBuffer();
try {
while ((st = reader.readLine()) != null) {
xStr = new StringBuffer(st.substring(0, st.indexOf(',')));
yStr = new StringBuffer(st.substring(st.indexOf(',') + 2,
st.length()));
Point2D pt = new Point2D.Double(
new Double(xStr.toString()).doubleValue(), new Double(
yStr.toString()).doubleValue());
pointList.add(pt);
}
} catch (Exception e) {
e.printStackTrace();
try {
reader.close();
} catch (Exception e2) {
e.printStackTrace();
}
}
return pointList;
}
public List<Point2D> convertLatLongToCoord(List<Point2D> coordinate) {
List<Point2D> latLong = new ArrayList<Point2D>();
double westMostLong = -79.974642;
double eastMostLong = -79.971244;
double longDiff = eastMostLong - westMostLong; // (rightmost_longitude -
// leftmost_longitude)
double northMostLat = 39.647556;
double southMostLat = 39.644675;
double latDiff = northMostLat - southMostLat; // (topmost_latitude -
// bottommost_latitude)
for (Point2D coord : coordinate) {
double j = coord.getY();
double k = coord.getX();
double XPixel = (((eastMostLong - j) / longDiff) * 1653);
double YPixel = (((northMostLat - k) / latDiff) * 1012);
Point2D actualCoord = new Point2D.Double(XPixel, YPixel);
latLong.add(actualCoord);
}
return latLong;
}
}
Some of the GPS lat/long I got from GPS sensors
Input Latitude Input Longitude
(39.64581, -79.97168)
(39.64651, -79.97275)
(39.646915, -79.97342)
(39.646538, -79.97279)
[IMG]http://i59.tinypic.com/nbqkk3.png[/IMG]
The red line in the picture shows the path followed when GPS coordinates were taken by sensor.
However, when I am using this formula to convert the Lat/Long coordinates to pixel coordinates. The pixel coordinates after conversion are not consistent, as you can see the output below:
Image X Image Y
(212.0977045, 613.3120444)
(732.6127134, 367.4251996)
(1058.542672, 225.1620965)
(752.0712184, 357.5897258)
The variation in the X,Y (pixel) coordinates are too much. So when I try to move a vehicle based on the pixel coordinates, the vehicle does not follow the red line or atleast near to that.
The vehicle moves either above the red line or below the line, but not on the line.
For smooth movement of the vehicle based on pixel coordinates, ideally I expect the conversion from lat/long to image coordinates to be something like this:
Required Image X Required Image Y
(1290, 409)
(1289, 409)
(1288, 409)
(1287, 409)
But I am getting this
Image X Image Y
(212.0977045, 613.3120444)
(732.6127134, 367.4251996)
(1058.542672, 225.1620965)
(752.0712184, 357.5897258)
I hope I am able to convey my problem.
Latitude and Longitude are not distances.
http://geography.about.com/cs/latitudelongitude/a/latlong.htm
I recently worked on a Arduino Project which was using GPS. I followed minigeo API approach that was converting latitude and longitude into northing and easting(UTM).
Using the library in the link you can do this convertion:
http://www.ibm.com/developerworks/library/j-coordconvert/
Than get maximum easting and northing and calculate the scale
private synchronized void scale() {
int w = 800;
int h = 600;
this.scale = Math.min(
w / (maxEasting - minEasting),
h / (maxNorthing - minNorthing));
oEasting = minEasting;
oNorthing = minNorthing;
}
Than converting to X and Y
private int applyScale(double km) {
return (int) (km * scale);
}
private int convertX(double easting) {
return applyScale(easting - oEasting);
}
private int convertY(double northing, int height) {
return 600/*height*/ - applyScale(northing - oNorthing);
}
Source:Minigeo
Here is a code that compiles and answers your question as
[1] (39.64581,-79.97168) -> 102,363
[2] (39.64651,-79.97275) -> 354,217
[3] (39.646915,-79.97342) -> 512,133
[4] (39.646538,-79.97279) -> 363,212
[5] (39.646458,-79.97264) -> 328,228
You might have interchanged the x-y coordinates. In this case, x == longitude, y == latitude.
import java.util.*;
import java.awt.geom.*;
public class LatLong {
private int imageW, imageH;
private final static double west = -79.974642, north = 39.647556,
east = -79.971244, south = 39.644675;
public LatLong (int w, int h) {
imageW = w;
imageH = h;
}
public List<Point2D> convertLatLongToCoord (List<Point2D> coordinate) {
List<Point2D> latLong = new ArrayList<Point2D>();
for (Point2D coord : coordinate) {
double x = coord.getY(), px = imageW * (x-east) / (west-east),
y = coord.getX(), py = imageH * (y-north)/(south-north);
latLong.add (new Point2D.Double(px,py));
}
return latLong;
}
public static void main (String[] args) {
double[] latit = {39.64581, 39.64651, 39.646915, 39.646538, 39.646458},
longit = {-79.97168, -79.97275, -79.97342, -79.97279, -79.97264};
List<Point2D> pointList = new ArrayList<Point2D>();
for (int i = 0 ; i < latit.length ; i++)
pointList.add (new Point2D.Double(latit[i], longit[i]));
List<Point2D> pixels = new LatLong (800,600).convertLatLongToCoord (pointList);
for (int i = 0 ; i < latit.length ; i++)
System.out.println ("[" + (i+1) + "]\t(" + latit[i] + "," + longit[i] + ") -> " +
(int) (pixels.get(i).getX()) + "," + (int) (pixels.get(i).getY()));
}}

Getting lon/lat from pixel coords in Google Static Map

I have a JAVA project to do using Google Static Maps and after hours and hours working, I can't get a thing working, I will explain everything and I hope someone will be able to help me.
I am using a static map (480pixels x 480pixels), the map's center is lat=47, lon=1.5 and the zoom level is 5.
Now what I need is being able to get lat and lon when I click a pixel on this static map. After some searches, I found that I should use Mercator Projection (right ?), I also found that each zoom level doubles the precision in both horizontal and vertical dimensions but I can't find the right formula to link pixel, zoom level and lat/lon...
My problem is only about getting lat/lon from pixel, knowing the center's coords and pixel and the zoom level...
Thank you in advance !
Use the Mercator projection.
If you project into a space of [0, 256) by [0,256]:
LatLng(47,=1.5) is Point(129.06666666666666, 90.04191318303863)
At zoom level 5, these equate to pixel coordinates:
x = 129.06666666666666 * 2^5 = 4130
y = 90.04191318303863 * 2^5 = 2881
Therefore, the top left of your map is at:
x = 4130 - 480/2 = 4070
y = 2881 - 480/2 = 2641
4070 / 2^5 = 127.1875
2641 / 2^5 = 82.53125
Finally:
Point(127.1875, 82.53125) is LatLng(53.72271667491848, -1.142578125)
Google-maps uses tiles for the map to efficient divide the world into a grid of 256^21 pixel tiles. Basically the world is made of 4 tiles in the lowest zoom. When you start to zoom you get 16 tiles and then 64 tiles and then 256 tiles. It basically a quadtree. Because such a 1d structure can only flatten a 2d you also need a mercantor projection or a conversion to WGS 84. Here is a good resource Convert long/lat to pixel x/y on a given picture. There is function in Google Maps that convert from lat-long pair to pixel. Here is a link but it says the tiles are 128x128 only: http://michal.guerquin.com/googlemaps.html.
Google Maps V3 - How to calculate the zoom level for a given bounds
http://www.physicsforums.com/showthread.php?t=455491
Based on the math in Chris Broadfoot's answer above and some other code on Stack Overflow for the Mercator Projection, I got this
public class MercatorProjection implements Projection {
private static final double DEFAULT_PROJECTION_WIDTH = 256;
private static final double DEFAULT_PROJECTION_HEIGHT = 256;
private double centerLatitude;
private double centerLongitude;
private int areaWidthPx;
private int areaHeightPx;
// the scale that we would need for the a projection to fit the given area into a world view (1 = global, expect it to be > 1)
private double areaScale;
private double projectionWidth;
private double projectionHeight;
private double pixelsPerLonDegree;
private double pixelsPerLonRadian;
private double projectionCenterPx;
private double projectionCenterPy;
public MercatorProjection(
double centerLatitude,
double centerLongitude,
int areaWidthPx,
int areaHeightPx,
double areaScale
) {
this.centerLatitude = centerLatitude;
this.centerLongitude = centerLongitude;
this.areaWidthPx = areaWidthPx;
this.areaHeightPx = areaHeightPx;
this.areaScale = areaScale;
// TODO stretch the projection to match to deformity at the center lat/lon?
this.projectionWidth = DEFAULT_PROJECTION_WIDTH;
this.projectionHeight = DEFAULT_PROJECTION_HEIGHT;
this.pixelsPerLonDegree = this.projectionWidth / 360;
this.pixelsPerLonRadian = this.projectionWidth / (2 * Math.PI);
Point centerPoint = projectLocation(this.centerLatitude, this.centerLongitude);
this.projectionCenterPx = centerPoint.x * this.areaScale;
this.projectionCenterPy = centerPoint.y * this.areaScale;
}
#Override
public Location getLocation(int px, int py) {
double x = this.projectionCenterPx + (px - this.areaWidthPx / 2);
double y = this.projectionCenterPy + (py - this.areaHeightPx / 2);
return projectPx(x / this.areaScale, y / this.areaScale);
}
#Override
public Point getPoint(double latitude, double longitude) {
Point point = projectLocation(latitude, longitude);
double x = (point.x * this.areaScale - this.projectionCenterPx) + this.areaWidthPx / 2;
double y = (point.y * this.areaScale - this.projectionCenterPy) + this.areaHeightPx / 2;
return new Point(x, y);
}
// from https://stackoverflow.com/questions/12507274/how-to-get-bounds-of-a-google-static-map
Location projectPx(double px, double py) {
final double longitude = (px - this.projectionWidth/2) / this.pixelsPerLonDegree;
final double latitudeRadians = (py - this.projectionHeight/2) / -this.pixelsPerLonRadian;
final double latitude = rad2deg(2 * Math.atan(Math.exp(latitudeRadians)) - Math.PI / 2);
return new Location() {
#Override
public double getLatitude() {
return latitude;
}
#Override
public double getLongitude() {
return longitude;
}
};
}
Point projectLocation(double latitude, double longitude) {
double px = this.projectionWidth / 2 + longitude * this.pixelsPerLonDegree;
double siny = Math.sin(deg2rad(latitude));
double py = this.projectionHeight / 2 + 0.5 * Math.log((1 + siny) / (1 - siny) ) * -this.pixelsPerLonRadian;
Point result = new org.opencv.core.Point(px, py);
return result;
}
private double rad2deg(double rad) {
return (rad * 180) / Math.PI;
}
private double deg2rad(double deg) {
return (deg * Math.PI) / 180;
}
}
Here's a unit test for the original answer
public class MercatorProjectionTest {
#Test
public void testExample() {
// tests against values in https://stackoverflow.com/questions/10442066/getting-lon-lat-from-pixel-coords-in-google-static-map
double centerLatitude = 47;
double centerLongitude = 1.5;
int areaWidth = 480;
int areaHeight = 480;
// google (static) maps zoom level
int zoom = 5;
MercatorProjection projection = new MercatorProjection(
centerLatitude,
centerLongitude,
areaWidth,
areaHeight,
Math.pow(2, zoom)
);
Point centerPoint = projection.projectLocation(centerLatitude, centerLongitude);
Assert.assertEquals(129.06666666666666, centerPoint.x, 0.001);
Assert.assertEquals(90.04191318303863, centerPoint.y, 0.001);
Location topLeftByProjection = projection.projectPx(127.1875, 82.53125);
Assert.assertEquals(53.72271667491848, topLeftByProjection.getLatitude(), 0.001);
Assert.assertEquals(-1.142578125, topLeftByProjection.getLongitude(), 0.001);
// NOTE sample has some pretty serious rounding errors
Location topLeftByPixel = projection.getLocation(0, 0);
Assert.assertEquals(53.72271667491848, topLeftByPixel.getLatitude(), 0.05);
// the math for this is wrong in the sample (see comments)
Assert.assertEquals(-9, topLeftByPixel.getLongitude(), 0.05);
Point reverseTopLeftBase = projection.projectLocation(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
Assert.assertEquals(121.5625, reverseTopLeftBase.x, 0.1);
Assert.assertEquals(82.53125, reverseTopLeftBase.y, 0.1);
Point reverseTopLeft = projection.getPoint(topLeftByPixel.getLatitude(), topLeftByPixel.getLongitude());
Assert.assertEquals(0, reverseTopLeft.x, 0.001);
Assert.assertEquals(0, reverseTopLeft.y, 0.001);
Location bottomRightLocation = projection.getLocation(areaWidth, areaHeight);
Point bottomRight = projection.getPoint(bottomRightLocation.getLatitude(), bottomRightLocation.getLongitude());
Assert.assertEquals(areaWidth, bottomRight.x, 0.001);
Assert.assertEquals(areaHeight, bottomRight.y, 0.001);
}
}
If you're (say) working with aerial photography, I feel like the algorithm doesn't take into account the stretching effect of the mercator projection, so it might lose accuracy if your region of interest isn't relatively close to the equator. I guess you could approximate it by multiplying your x coordinates by cos(latitude) of the center?
It seems worth mentioning that you can actually have the google maps 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.
UPDATE: I realized that I did this two ways, both still utilizing the same way of creating the overlay (so I won't duplicate that code).
let point = new google.maps.Point(628.4160703464878, 244.02779437950872);
console.log(point);
let overlayProj = overlay.getProjection();
console.log(overlayProj);
let latLngVar = overlayProj.fromContainerPixelToLatLng(point);
console.log('the latitude is: '+latLngVar.lat()+' the longitude is: '+latLngVar.lng());

Categories

Resources