Eccentricity Vector in Java not accurate as orbit moves along - java

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

Related

How do I add some multiplayer code (for java) that does simple things like updates clients' positions

How do I add some code into my multiplayer game that I am creating using ThinMatrix's tutorials on YouTube? I have been following his tutorials when I decided to go... well... on a tangent and start adding my own things to my game. Could someone help me with some code that updates the position of player entities in the game? I already know how to do (basic) networking.
Thank you!
Edit: Code added from comments
Camera Class:
package entities;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.util.vector.Vector3f;
public class Camera {
private float distanceFromPlayer = 35;
private float angleAroundPlayer = 0;
private Vector3f position = new Vector3f(0, 0, 0);
private float pitch = 20;
private float yaw = 0;
private float roll;
private Player player;
public Camera(Player player){
this.player = player;
}
public void move(){
calculateZoom();
calculatePitch();
calculateAngleAroundPlayer();
float horizontalDistance = calculateHorizontalDistance();
float verticalDistance = calculateVerticalDistance();
calculateCameraPosition(horizontalDistance, verticalDistance);
this.yaw = 180 - (player.getRotY() + angleAroundPlayer);
}
public Vector3f getPosition() {
return position;
}
public float getPitch() {
return pitch;
}
public float getYaw() {
return yaw;
}
public float getRoll() {
return roll;
}
private void calculateCameraPosition(float horizDistance, float verticDistance){
float theta = player.getRotY() + angleAroundPlayer;
float offsetX = (float) (horizDistance * Math.sin(Math.toRadians(theta)));
float offsetZ = (float) (horizDistance * Math.cos(Math.toRadians(theta)));
position.x = player.getPosition().x - offsetX;
position.z = player.getPosition().z - offsetZ;
position.y = player.getPosition().y + verticDistance + 4;
}
private float calculateHorizontalDistance(){
return (float) (distanceFromPlayer * Math.cos(Math.toRadians(pitch+4)));
}
private float calculateVerticalDistance(){
return (float) (distanceFromPlayer * Math.sin(Math.toRadians(pitch+4)));
}
private void calculateZoom(){
float zoomLevel = Mouse.getDWheel() * 0.03f;
distanceFromPlayer -= zoomLevel;
if(distanceFromPlayer<5){
distanceFromPlayer = 5;
}
}
private void calculatePitch(){
if(Mouse.isButtonDown(1)){
float pitchChange = Mouse.getDY() * 0.2f;
pitch -= pitchChange;
if(pitch < 0){
pitch = 0;
}else if(pitch > 90){
pitch = 90;
}
}
}
private void calculateAngleAroundPlayer(){
if(Mouse.isButtonDown(0)){
float angleChange = Mouse.getDX() * 0.3f;
angleAroundPlayer -= angleChange;
}
}
}
Player Class:
package entities;
import models.TexturedModel;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.vector.Vector3f;
import renderEngine.DisplayManager;
import terrains.Terrain;
public class Player extends Entity {
private static final float RUN_SPEED = 40;
private static final float TURN_SPEED = 160;
private static final float GRAVITY = -50;
private static final float JUMP_POWER = 18;
private float currentSpeed = 0;
private float currentTurnSpeed = 0;
private float upwardsSpeed = 0;
private boolean isInAir = false;
public Player(TexturedModel model, Vector3f position, float rotX, float rotY, float rotZ,
float scale) {
super(model, position, rotX, rotY, rotZ, scale);
}
public void move(Terrain terrain) {
checkInputs();
super.increaseRotation(0, currentTurnSpeed * DisplayManager.getFrameTimeSeconds(), 0);
float distance = currentSpeed * DisplayManager.getFrameTimeSeconds();
float dx = (float) (distance * Math.sin(Math.toRadians(super.getRotY())));
float dz = (float) (distance * Math.cos(Math.toRadians(super.getRotY())));
super.increasePosition(dx, 0, dz);
upwardsSpeed += GRAVITY * DisplayManager.getFrameTimeSeconds();
super.increasePosition(0, upwardsSpeed * DisplayManager.getFrameTimeSeconds(), 0);
float terrainHeight = terrain.getHeightOfTerrain(getPosition().x, getPosition().z);
if (super.getPosition().y < terrainHeight) {
upwardsSpeed = 0;
isInAir = false;
super.getPosition().y = terrainHeight;
}
}
private void jump() {
if (!isInAir) {
this.upwardsSpeed = JUMP_POWER;
isInAir = true;
}
}
private void checkInputs() {
if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
this.currentSpeed = RUN_SPEED;
} else if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
this.currentSpeed = -RUN_SPEED;
} else {
this.currentSpeed = 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
this.currentTurnSpeed = -TURN_SPEED;
} else if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
this.currentTurnSpeed = TURN_SPEED;
} else {
this.currentTurnSpeed = 0;
}
if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
jump();
}
}
}
You need to make it so your player class can return the current locations and doing so makes things easy as all you need to do is create another instance of the game with new packages and run it but on the server end you need to send the players current location and make this update consistently as this would use a coordinate system but this is very basic so you will already need an entity in the game such as a player entity in the server side of the game and doing so you need this entity to update its position so you could do something like player.getPosition() and then serverSideModel = player.getPosition() as it would set both models to the same position if that makes sence
The task you want to achieve is a simple one, but because of the way your code is written it has become an unnecessarily hard task to achieve. Fixing your code is not something that you are ready for without understanding more about Object Oriented code and efficient networking.
So at this point I will not attempt to fix your code but I will simply suggest a way to make it work somewhat, and be aware that you will have a lot of issues later if you continue this project.
First we need a simple way to deal with updating the position of a Player entity. We can do this by adding a method to your Player class a bit like so:
public void moveByFloatInput(fload newX, float newY, float newY) {
//Here you need to get the current terrain object for this player entity
Terrain currentTerrain = getTerrain();
//Now update the terrain object with your new x,y,z points (not sure what methods Terrain contains, you may need to make changes or expose more variables)
currentTerrain.setNewPosition(newX, newY, newZ);
//Now call the normal move method using the updated terrain with a new position
move(updatedTerrainObject);
}
Note: there may be a better way to do this, but I have no idea how the terrain class is constructed, and what methods can be called. Also note that you will want to pass the direction the player is looking, but to keep this example simple I have not included any of that.
Now when we receive the x y and z info from a client/server we can update a player entity, for example if you where using an InputStreamReader (not the best idea, but it will help you get started):
float x;
float y;
float z;
while ((message = myBufferedReaderInputFromSocket.readLine()) != null) {
if (message != null) {
//check if X
if (message.startsWith("moveX"))
//save new X so we can update a player entity
x = Float.parseFloat(message.substring(5));
//check if Y
if (message.startsWith("moveY"))
//save new Y so we can update a player entity
y = Float.parseFloat(message.substring(5));
//check if Z
if (message.startsWith("moveZ"))
//save new Z so we can update a player entity
z = Float.parseFloat(message.substring(5));
}
//when x, y and y all have a new position we can update a player entity
if(x != null && y != null && z != null)
{
//call our new player move method and change that player entities position
playerEntityForThisSocket.moveByFloatInput(x, y, z);
//reset x,y,z to null so that the server can receive the next movement
x = null;
y = null;
z = null;
}
}
Then for sending over a socket you can do it a bit like:
//you could create a Print writer for your socket:
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
//then simply send a players new position info
//inculde the "moveX" keyword before the Float x,y,z so that the server/client at the other end knows how to process the info:
out.println(moveX + newXposition);
out.println(moveY + newYposition);
out.println(moveZ + newZposition);
If this does not make sense then I suggest that you complete the basic Java tutorials before you continue coding, because the tutorials contain the essential building blocks of coding with Java, and with that knowledge that you can build on to make a great project, but without that knowledge you are doomed to have never ending issues:
http://docs.oracle.com/javase/tutorial/index.html

Java - using multiple rates to calculate time

I have a homework problem that I could use a little help with.
I have an exploratory robot that I need to create a class for in which I can command it to move and return various different things about its status.
It has an initial rate at which it is moving of 5.0 units. I used this rate and the distance is has traveled to calculate the time it has been exploring.
I have to create a method that allows the user to change the rate multiple times and still calculate the time it has been traveling, with each part calculated at the rate it was traveling at the time.
Ex: The robot moves 50 fields at the rate of 5.0, then the rate is changed to 6.0 for 20 fields, then 2.0 for 80 fields.
Something along those lines.
Here's what I have for the object class so far.
private int xcoord, ycoord;
private int identification;
private double rate;
private double traveled;
//Sets up a robot with the given ID number and beginning x and y coordinates
public Robot (int id, int x, int y)
{
identification = id;
xcoord = x;
ycoord = y;
traveled = 0;
rate = 5.0;
}
//Has the robot travel to the set coordinates
public double setDestination (int x, int y)
{
double distance = Math.pow(x - xcoord, 2) + Math.pow(y - ycoord, 2);
traveled += Math.sqrt(distance);
xcoord = x;
ycoord = y;
return traveled;
}
//Gets the time spent travelling
public double getTimeSpent()
{
return traveled/rate;
}
//Sets the rate at which the robot travels
public void setRate(double setrate)
{
rate = setrate;
}
//Returns the ID of the robot
public int getID()
{
return identification;
}
I figure I will need to change my getTimeSpent() method, but I'm not sure how to change it so that it takes each leg of the journey at its individual rate. As set up right now, it will return the time of the whole journey at the last rate that was set.
Thanks for any help.
Couldn't you do something along the lines of this? Pretty much create an instance variable timeSpent and update that in setDestination using the distance traveled that time only instead of the whole distance, with the current rate and return it in getTimeSpent
private int xcoord, ycoord;
private int identification;
private double rate;
private double traveled;
private double timeSpent;
//Sets up a robot with the given ID number and beginning x and y coordinates
public Robot (int id, int x, int y)
{
identification = id;
xcoord = x;
ycoord = y;
traveled = 0;
rate = 5.0;
}
//Has the robot travel to the set coordinates
public double setDestination (int x, int y)
{
double distance = Math.pow(x - xcoord, 2) + Math.pow(y - ycoord, 2);
traveled += Math.sqrt(distance);
xcoord = x;
ycoord = y;
timeSpent += Math.sqrt(distance)/rate;
return traveled;
}
//Gets the time spent travelling
public double getTimeSpent()
{
return timeSpent;
}
//Sets the rate at which the robot travels
public void setRate(double setrate)
{
rate = setrate;
}
//Returns the ID of the robot
public int getID()
{
return identification;
}
Introduce another member to the class, say travelLog, which will be a list of pairs TravelLogEntry: (rate, distanceTraveledAtRate) -- that can be another class you'll have to create.
Something like this:
List<TravelLogEntry> travelLog = new ArrayList<TravelLogEntry>();
In setRate() you'll need to save the current rate and the distance traveled at the current rate to the travelLog, and then set the new rate:
travelLog.add(new TravelLogEntry(this.rate, this.traveled));
this.rate = newRate;
distance = 0;
In getTimeSpent() iterate over travelLog and compute time spent for each of its entries while accumulating the time in some temporary variable:
long totalTimeSpent = 0;
// go throught all the previous rates/distances
for(TravelLogEntry entry: travelLog){
totalTimeSpent += entry.traveled/entry.rate;
}
// and don't forget the current rate:
totalTimeSpent += traveled/rate;
In the end totalTimeSpent will contain the total time the robot traveled, adjusted to different travel rates.
This should do the trick.
UPDATE: Nevermind, I guess, #austin's solution is way simpler.

Hexagonal Grid (Flat Top) Distance Calculation

I have been working on creating a hexagonal (flat top) grid for a simulation I am working on. I have attempted to work out the distance between the hexagons, from a specified target hexagon.
The solution I have works for most of the time, apart from every odd column from the target hexagon north of the target is shifted up by 1. I know that sounds confusing but I have attached an image to explain what I mean:
As you guys can see, the bottom half of the grid below the target hexagon and every other column above the target Hexagon is correct. I cannot understand why :S
Here is an explanation of the Axial & Cube Co-ords.
http://www.redblobgames.com/grids/hexagons/#coordinates
Here is the code responsible for converting the Axial Co-ords to Cube Co-ords.
public void setQR(int theQ, int theR){
this.q = theQ;
this.r = theR;
this.x = this.q;
this.z = this.r - (this.q - (this.q&1)) /2;
this.y = -(this.x + this.z);
}
And heres the code for working out distance.
FYI, the Hexagons are created from a CentrePoint (CPx, CPy).
private double distance = 0;
public double workOutDistance(Hexagon hexagon, HexagonFood target){
double targetX = target.getCPX();
double targetY = target.getCPY();
double hexagonX = hexagon.getCPX();
double hexagonY = hexagon.getCPY();
double deltaX = (targetX-hexagonX)*-1;
double deltaY = (targetY-hexagonY)*-1;
double deltaXRadius = (deltaX/(SimField.hexSize)/1.5);
double deltaYApothem = (deltaY/(SimField.hexSize/1.155)/2);
hexagon.setQR((int)deltaXRadius, (int)deltaYApothem);
ArrayList<Integer> coords = new ArrayList<>();
coords.add(
Math.abs(hexagon.getX() - target.getX())
);
coords.add(
Math.abs(hexagon.getZ() - target.getZ())
);
coords.add(
Math.abs(hexagon.getY() - target.getY())
);
System.out.println(coords);
distance = Collections.max(coords);
return distance;
}
Can anyone please tell me why this is happening ? Would be greatly appreciated.
EDIT:
After changing Int to Double as suggested by Tim, I get this.
http://i.stack.imgur.com/javZb.png
**
SOLUTION
**
after experimenting with the answers given, This small tweak solves the problem.
changing this..
public void setQR(int theQ, int theR){
this.q = theQ;
this.r = theR;
this.x = this.q;
this.z = this.r - (this.q - (this.q&1)) /2;
this.y = -(this.x + this.z);
}
to this..
public void setQR(int theQ, int theR){
this.q = theQ;
this.r = theR;
this.x = this.q;
if (this.r>0){
this.z = this.r - (this.q - (this.q&1))/2;
}
else {
this.z = this.r - (this.q + (this.q&1))/2;
}
this.y = -(this.x + this.z);
}
You're casting a double to an int when calling setQR(); are you sure that's doing what you expect? Doubles use floating point math, so the number you'd expect to be 2.0 might actually be 1.999999989, which would then be rounded down to 1 when cast to an int.
I'm also skeptical of the line that reads this.z = this.r - (this.q - (this.q&1)) /2;. You're adding 1 when the number is odd, which seems to be the failure case you're experiencing; I'd make sure that line is doing what you're expecting, too.
If you're not stepping through this with a debugger and examining the values, you're doing it wrong.
You could also take an entirely different approach to this problem. You know the X/Y (cartesian) coordinates of your two hexagons, which means you can get each hexagon's cubic coordinates relative to the origin of your hexagonal space. The distance between the two hexagons is simply the sum of the absolute values of the differences between the two hexagons' X, Y and Z cubic coordinates. (That is, dist = |h2.X - h1.X| + |h2.Y - h1.Y| + |h2.Z - h1.Z|) So rather than trying to compute the vector between the two centerpoints and then convert that into cubic coordinates, you could just compute the distance directly in cubic coordinates (just like you would if these were squares in cartesian coordinates)...
Even if you take this approach, though, I'd strongly recommend that you debug what's going on with your original approach. Even if you end up throwing away the code, the exercise of debugging will probably teach you valuable lessons that you'll be able to apply in the future.
Note to readers: "cubic" coordinates aren't 3-dimensional cartesian coordinates, they're a hexagon-specific coordinate system for which a link was provided by the OP.
The fact that the computation (that is, the conversion from offset- to cube coordinates, and the computation of the distance in cube coordinates) seems to be correct suggests that Tim was right with his assumption about the floating point errors.
You should try to change the line
hexagon.setQR((int)deltaXRadius, (int)deltaYApothem);
from your original code to something like
hexagon.setQR((int)Math.round(deltaXRadius), (int)Math.round(deltaYApothem));
Which could solve the issue in this case.
If not ... or... in any case, here's a small example, basically doing the same as you did, but as a MVCE...
import java.awt.Point;
public class HexagonsTest
{
public static void main(String[] args)
{
// Above and below
test(8,6, 8,5, 1);
test(8,6, 8,7, 1);
// Left
test(8,6, 7,5, 1);
test(8,6, 7,6, 1);
// Right
test(8,6, 9,5, 1);
test(8,6, 9,6, 1);
// The first one that was wrong:
test(8,6, 7,4, 2);
}
private static void test(int x0, int y0, int x1, int y1, int expected)
{
int distance = computeStepsDistance(x0, y0, x1, y1);
System.out.println(
"Distance of (" + x0 + "," + y0 + ") to " +
"(" + x1 + "," + y1 + ") is " + distance +
", expected " + expected);
}
private static int computeStepsDistance(int x0, int y0, int x1, int y1)
{
Point cp0 = convertOffsetToCubeCoordinates(x0, y0, null);
Point cp1 = convertOffsetToCubeCoordinates(x1, y1, null);
int cx0 = cp0.x;
int cy0 = cp0.y;
int cz0 = -cx0-cy0;
int cx1 = cp1.x;
int cy1 = cp1.y;
int cz1 = -cx1-cy1;
int dx = Math.abs(cx0 - cx1);
int dy = Math.abs(cy0 - cy1);
int dz = Math.abs(cz0 - cz1);
return Math.max(dx, Math.max(dy, dz));
}
private static Point convertOffsetToCubeCoordinates(
int ox, int oy, Point p)
{
int cx = ox;
int cz = oy - (ox - (ox&1)) / 2;
int cy = -cx-cz;
if (p == null)
{
p = new Point();
}
p.x = cx;
p.y = cy;
return p;
}
}

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());

How to check if VERTEX ARRAY is clockwise?

Solved, used this code:
if ( !isClockwise(TempVectArray) ) { Collections.reverse(TempVectArray); }
...
private boolean isClockwise(ArrayList<Vec2> arl){
Iterator<Vec2> it = arl.iterator();
Vec2 pt1 = (Vec2)it.next();
Vec2 firstPt = pt1;
Vec2 lastPt = null;
double area = 0.0;
while(it.hasNext()){
Vec2 pt2 = (Vec2) it.next();
area += (((pt2.x - pt1.x) * (pt2.y + pt1.y)) / 2);
pt1 = pt2;
lastPt = pt1;
}
area += (((firstPt.x - lastPt.x) * (firstPt.y + lastPt.y)) / 2);
return area < 0;
}
Suppose I get a vertex array from the user tapping on the screen, but need it to be clockwise.
Maybe you know of some standard methods to check if it is clockwise and if it's not, then make it clockwise?
Thanks!
One way to do it is to first calculate the average point, and then sort everything around it by angle. Should be something like this:
public static void sortPointsClockwise(ArrayList<PointF> points) {
float averageX = 0;
float averageY = 0;
for (PointF point : points) {
averageX += point.x;
averageY += point.y;
}
final float finalAverageX = averageX / points.size();
final float finalAverageY = averageY / points.size();
Comparator<PointF> comparator = new Comparator<PointF>() {
public int compare(PointF lhs, PointF rhs) {
double lhsAngle = Math.atan2(lhs.y - finalAverageY, lhs.x - finalAverageX);
double rhsAngle = Math.atan2(rhs.y - finalAverageY, rhs.x - finalAverageX);
// Depending on the coordinate system, you might need to reverse these two conditions
if (lhsAngle < rhsAngle) return -1;
if (lhsAngle > rhsAngle) return 1;
return 0;
}
};
Collections.sort(points, comparator);
}
public static void sortPointsCounterClockwise(ArrayList<PointF> points) {
sortPointsClockwise(points);
Collections.reverse(points);
}
You have the sequence numbers and positions of the nodes. Get the movements which hold x and y changes in the move. All left to do is define a control structure such as:
if(movement_before is "up")
movement should-be "up" or "up-right"
if(movement_before is "up-left")
movement should-be "up" or "up-left" or "up-right"
etc..

Categories

Resources