Calculating new set of waypoints given current GPS points

Hi guys,

Just mucking around with things at the moment, however for the life of me, I cant see what is the issue. I know it will be something simple, but im not picking it up.

I have a set of long, lat coordinates that come in from my GPS module, reading that is fine, no issues thus far.

I then take these coordinates and then attempt to project them a given distance at a given heading.
I know what/where the new coordinates should work out to be, however, my code ain't playing ball.

Here is what I have got so far

#include <SoftwareSerial.h>

#include <TinyGPS.h>
#include <math.h>

TinyGPS gps;
SoftwareSerial GPS(10,11);  

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  GPS.begin(9600);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  
float lat, lon;
static float lat1, lon1;
 float brng;
  float d, R;
  brng = 90*(PI/180);  //Bearing set (which will actually be compass heading)
  d = 1000;   //set distance
  R = 6371000; //radius of earth
 
while(GPS.available()){ // check for gps data
   if(gps.encode(GPS.read())){ // encode gps data
      gps.f_get_position(&lat,&lon); // get latitude and longitude
    
    // display position
    Serial.print("Position: ");
    Serial.print("lat: ");Serial.print(lat, 8);Serial.print(" ");// print latitude
    Serial.print("lon: ");Serial.println(lon, 8); // print longitude
 

    lat1 =asin(sin(lat)*cos(d/R) + cos(lat)*sin(d/R)*cos(brng));
    lon1 = lon + atan2(cos(d/R)-sin(lat)*sin(lat), sin (brng)*sin(d/R)*sin(lat));
    Serial.println ();
    Serial.print("Waypoint Lat: ");Serial.print(lat1, 8);Serial.print(" ");// print new latitude
    Serial.print("Waypoint Lon: ");Serial.println(lon1, 8); // print new longitude
    Serial.println ();
    Serial.println ();
 
delay(5000);
  
}
}
}

Is float really suitable for R?

Floats have a precision of 6-7 characters, so wont this bit cause problems?

R = 6371000; //radius of earth

You have several references to d/R, since these are both constants, why not replace with 6371?

I have made some progress I believe, I needed to first change all values including latest and long into radians to run through the above equations.

My latest value now seems mor appropriate, however haven't had time enough to look at it enough.

I belive you are correct regarding the float use for R. However, it's 1000/6731000 = ~0.001569.

I have made some progress I believe, I needed to first change all values including latest and long into radians to run through the above equations.

My latest value now seems mor appropriate, however haven't had time enough to look at it enough.

I belive you are correct regarding the float use for R. However, it's 1000/6731000 = ~0.001569.

change all values including latest and long into radians

ahh yes, the classic navigation error. All great-circle maths needs to use radians not degrees. This is one of my favorite mistakes - no matter how many times it catches me out I always manage to do it again!

I come from a physics background and radians still get me!

I'll have another go at it tomorrow and see how things pan out. Eventually I would like the code to navigate to the waypoint via a servo, using gps and a compass. I'm sure that will be a bit harder!

You can remember Pi (3.14159...something,, something, forever), but 57.33 still gets you? I still can't figure out what drove some Greek to come up with Pi......
You got most of the problem figured out. Gonna use a compass driven servo as a directional pointer.... Might think about something like a MPU6050 to get the positional deltas and update the servo, long before interfacing the servo. Updating the servo will be a snap once you calculate the deviation from relative zero. which will change when the user changes orientation in an attempt to match the servo pointer,,, so you'll need to PID the servo input to compensate. Gonna get somewhat complicated, but has been done before.
sounds like fun. Good luck and I look forward to hearing about your progress.

The Arduino supports only single precision floating point calculations (6-7 digits), which is a severe limitation for navigation calculations.

Depending on where you are on Earth, you should expect something like +/- 100 meters accuracy for waypoint calculations. With some programming effort (like separating degrees and fractional degrees in all calculations), you can do better.

I see, that would definitely place me out on the final waypoint.

Huge accuracy is not really what im after, within 10 meters would be good enough.
I know I have definitely read some code out there that splits the fractional degrees does the calculation. Ill have to have a dig around again.

Once the calculations are done, then is it safe to merge them back together? Or for accuracy still treat them separately?

within 10 meters would be good enough.

If you make the programing effort, you can expect better than 1 meter precision. It is not straightforward, though. It is MUCH simpler and easier to use a platform, like ARM-based boards, that support double precision floats.

That said, there were several formula errors in your program so I fixed those and ran a simple example. It works surprisingly well for some calculations.

The following program calculates a waypoint 100 meters at bearing 90 degrees from the point 60 N 120 E. The distance between the two points thus produced is 100.5 meters, as calculated by the application on this handy web page: Calculate distance and bearing between two Latitude/Longitude points using haversine formula in JavaScript

Note that assumption of a perfectly spherical Earth leads to errors of up to 0.3%.

#include <math.h>

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  float lat=60.0, lon=120.0;
  static float lat1, lon1;
  float c=PI/180.;
  float brng;
  float d, R;
  brng = 90*c;  //Bearing set (which will actually be compass heading)
  d = 100;   //set distance
  R = 6378100; //radius of earth

      // display position
      Serial.print("Position: ");
      Serial.print("lat: ");
      Serial.print(lat, 6);
      Serial.print(" ");// print latitude
      Serial.print("lon: ");
      Serial.println(lon, 6); // print longitude

      lat=lat*c;
      lon=lon*c;
      lat1 =asin(sin(lat)*cos(d/R) + cos(lat)*sin(d/R)*cos(brng));
      lon1 = lon + atan2(sin (brng)*sin(d/R)*cos(lat), cos(d/R)-sin(lat)*sin(lat1));
      Serial.println ();
      Serial.print("Waypoint Lat: ");
      Serial.print(lat1/c, 6);
      Serial.print(" ");// print new latitude
      Serial.print("Waypoint Lon: ");
      Serial.println(lon1/c, 6); // print new longitude

}

void loop() {
  // put your main code here, to run repeatedly:
    }
1 Like

With some programming effort (like separating degrees and fractional degrees in all calculations), you can do better.

Have you done this? Or seen it been done? Can you provide formula (or links to formula). All the formulas I've seen for spherical geometry or great-circle maths require a high level of mathematical skill (by that I mean more skill than required for the 12 times table).

These are the formula I normally use...
http://williams.best.vwh.net/avform.htm

If you know how to re-work these equations to split the input parameters to degrees and fractions of degrees (or even better, into degrees (integers) minutes (integers) and seconds (integers)) then please publish!

Just seen your post jremington, I had noticed that there were some errors in the formula.

Out of interest, I ran with a simpler model (flat earth) and the results were again, not too bad.
Ill have a look at yours.

So far I have managed to integrate the appropriate modules; compass reads a heading, uses this for the bearing to calculate the new set of way points. Uses TinyGPS to get the course.

I understand this all falls under void setup, rather than the main loop.

Now I need to start working on getting the error from actual heading and proposed heading, and get this working with a PID for a servo.

  #include <SoftwareSerial.h>

#include <TinyGPS.h>
#include <math.h>
#include <HMC5883L.h>
#include <Wire.h>

TinyGPS gps;
SoftwareSerial ss(10,11);  

// Store our compass as a variable.
HMC5883L compass;
// Record any errors that may occur in the compass.
int error = 0;


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  ss.begin(9600);
  
 //Serial.println("Starting the I2C interface.");
 Wire.begin(); // Start the I2C interface.

 //Serial.println("Constructing new HMC5883L");
` compass = HMC5883L(); // Construct a new HMC5883 compass.
  
 //Serial.println("Setting scale to +/- 1.3 Ga");
 error = compass.SetScale(1.3); // Set the scale of the compass.
 //if(error != 0) // If there is an error, print it out.
  //Serial.println(compass.GetErrorText(error));
  
  //Serial.println("Setting measurement mode to continous.");
 error = compass.SetMeasurementMode(Measurement_SingleShot); // Set the measurement mode to Continuous
 if(error != 0) // If there is an error, print it out.
  Serial.println(compass.GetErrorText(error));
  
}

void loop() {
  // put your main code here, to run repeatedly:
  
float lat, lon, latrad, lonrad;
float trial;
int hdng;
static float lat1, lon1;
float brng, brngrad;
int d, R;  
  d = 1000;   //set distance
  R = 6371000; //radius of earth

        //Initating GPS
while(ss.available()){ // check for gps data
   if(gps.encode(ss.read())){ // encode gps data
      gps.f_get_position(&lat,&lon); // get latitude and longitude

      
         //COMPASS READINGS
     
  // Retrive the raw values from the compass (not scaled).
 MagnetometerRaw raw = compass.ReadRawAxis();
  // Retrived the scaled values from the compass (scaled to the configured scale).
 MagnetometerScaled scaled = compass.ReadScaledAxis();
 
 int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis)

 // Calculate heading when the magnetometer is level, then correct for signs of axis.
 float heading = atan2(scaled.YAxis, scaled.XAxis);
 
  float declinationAngle = 20.233*PI/180;
 heading += declinationAngle;
 
 // Correct for when signs are reversed.
 if(heading < 0)
  heading += 2*PI;
  
 // Check for wrap due to addition of declination.
 if(heading > 2*PI)
  heading -= 2*PI;
 
 // Convert radians to degrees for readability.
int  headingDegrees = heading * 180/M_PI;
 
 // brng = headingDegrees;
  brngrad = heading;// brng*(PI/180);  //Bearing set (which will actually be compass heading)

      // display position
    Serial.println();
    Serial.print("Current Position: ");
    Serial.print("lat: ");Serial.print(lat,6);Serial.print(" ");// print latitude
    Serial.print("lon: ");Serial.println(lon,6); // print longitude

    //Calculating the corodinates for hte next waypoint at given distance d. Used a flat earth modle,
    //as distances never exceed 2 km. 

 latrad=lat*(PI/180);
 lonrad=lon*(PI/180);

    lat1 = ((cos(brngrad)*d)/111111)+ lat;
    lon1 =(((d*sin(brngrad))/cos(latrad))/111111) + lon;
    
    Serial.println ();
    Serial.print("Waypoint Lat: ");Serial.print(lat1,6);Serial.print(" ");// print new latitude
    Serial.print("Waypoint Lon: ");Serial.println(lon1,6); // print new longitude
    Serial.println ();
    
    
hdng = TinyGPS::course_to(lat, lon, lat1, lon1);

Serial.print ("Course to Waypoint: "); Serial.print(hdng);
Serial.println();

 // Output the data via the serial port.
Serial.print ("Compass bearing: "); Serial.print(headingDegrees);
Serial.println();
 
//Runs dealy between next read

 delay(5000);

 
}
}
}

I am correct in thinking that I need two correction permutations, one to ensure that the boat it facing on the right heading (using the compass) and another to correct for lateral drift (from tides and wave action) - this would be done by comparing the current bearing to the Waypoint and comparing this with the target bearing (using the GPS).

Ive seen many threads on one or the other, but I believe to make this boat navigate to a waypoint, both a compass and GPS must be used. Correct me If I am wrong.

For instance, initially the heading and bearing are the same at the start point (say 90 degree or 090). As the boat starts to head out, waves and tide affect both the orientation of the boat and the bearing it is heading on. So now the heading and bearing are no longer the same (as they were at the start point).

That is my understanding, please feel free to add in.

A GPS will normally just tell you where you are, some NMEA sentences also include information about which direction you are moving (your course), but only a compass will tell you which way you are pointing (your bearing)

Once you've got these three factors, you'll then need to start doing so vector maths to work out which way you need to steer to get to a turnpoint.

If you're dealing with an aircraft, you'll also be interested in windspeed and direction, if you're playing with a boat, then in addition, you'll be effected by current strength and direction.

Thanks Fulliautomatix,

I understand the GPS only will calculate the direction your are moving. However my understanding is the course is the planned route, like above, the course was 090.

However at any given time, the GPS will calculate the direction you are travelling to a fixed position, the bearing, not the course.

Likewise the compass will indicate the direction your nose is pointing, known as the heading not your bearing.

Hence, my thinking is that in my code we are going to calculate the following error.

Heading (from compass) - initial course = heading error == brng error.

Initial course - brng error = new bearing (this new bearing can also be checked by looking at get_courseto from Tiny GPS library)

Therefore Heading - new bearing = adjustment error.

This adjustment error is the error fed into the PID loop, to adjust servo and rudder to the new bearing

If you know how to re-work these equations to split the input parameters to degrees and fractions of degrees (or even better, into degrees (integers) minutes (integers) and seconds (integers)) then please publish!

That would involve high-school trigonometric relationships, for example sum and difference angle formulas that you can look up, but it would be a complete waste of time and effort.

True double precision calculations (not supported by ATmega-based Arduinos) solve the problem with no extra effort at all.

it would be a complete waste of time and effort.

Why?

As you point out, double precision calculations are not available for the mega, so how could you accurately navigate using a mega?

Or are you just saying it's easier to upgrade your hardware platform than do the high school trig?

Judge for yourself! .......... replaced by one of these.

Thanks for the link, it's been a long time since high school. I feel some comparative testing coming on.....

zacl79:
Hi guys,

Just mucking around with things at the moment, however for the life of me, I cant see what is the issue. I know it will be something simple, but im not picking it up.

I have a set of long, lat coordinates that come in from my GPS module, reading that is fine, no issues thus far.

I then take these coordinates and then attempt to project them a given distance at a given heading.
I know what/where the new coordinates should work out to be, however, my code ain't playing ball.

Here is what I have got so far

#include <SoftwareSerial.h>

#include <TinyGPS.h>
#include <math.h>

TinyGPS gps;
SoftwareSerial GPS(10,11);

void setup() {
  // put your setup code here, to run once:

Serial.begin(9600);

GPS.begin(9600);
 
}

void loop() {
  // put your main code here, to run repeatedly:
 
float lat, lon;
static float lat1, lon1;
float brng;
  float d, R;
  brng = 90*(PI/180);  //Bearing set (which will actually be compass heading)
  d = 1000;  //set distance
  R = 6371000; //radius of earth

while(GPS.available()){ // check for gps data
  if(gps.encode(GPS.read())){ // encode gps data
      gps.f_get_position(&lat,&lon); // get latitude and longitude
   
    // display position
    Serial.print("Position: ");
    Serial.print("lat: ");Serial.print(lat, 8);Serial.print(" ");// print latitude
    Serial.print("lon: ");Serial.println(lon, 8); // print longitude

lat1 =asin(sin(lat)*cos(d/R) + cos(lat)*sin(d/R)*cos(brng));
    lon1 = lon + atan2(cos(d/R)-sin(lat)*sin(lat), sin (brng)*sin(d/R)*sin(lat));
    Serial.println ();
    Serial.print("Waypoint Lat: ");Serial.print(lat1, 8);Serial.print(" ");// print new latitude
    Serial.print("Waypoint Lon: ");Serial.println(lon1, 8); // print new longitude
    Serial.println ();
    Serial.println ();

delay(5000);
 
}
}
}

Hi, I know a lot time has passed but I will share this code fragment as it my be useful for someone, it is based on google maps SphericalUtil library.

GitHub - SphericalUtil_Arduino

I use the distance and direction calculation from the TinyGPS++ library. When the two waypoints come from two GPS 25M apart;

The distance calulation is normally within +- 10M, often less.

Direction is within +- 15 degrees.