3

I'm currently working on a project involving making a "parent-child distance warning system" with my Flora board + Ultimate GPS Module (https://www.adafruit.com/product/1059). I'm on the preliminary phase and just trying to retrieve the current GPS module position, comparing it with a pre-fixed position (expressed in GPS decimal coordinates, e.g. 40.779006, -74.289395), calculate the distance between these two points and, if the distance is more than a certain value, say 20 meters, print on serial monitor a warning message. I was able to correctly retrieve the GPS module position (expressed in decimal coordinates, as above), but when I try to calculate the distance I'm not able to achieve a few-meter accurancy.

So, my questions are:

  1. Is there a specific/best formula to calculate short distances (max 100 meters) on the earth surface with an accurancy of 2-3 meters? I tried with Haversine formula with not so bad results, but because of the scarce Double precision (in ATMega processors is the same of the Float one, i.e. 32 bits), I'm able to obtain a precision of only 6-7 digits in total (as stated in https://www.arduino.cc/en/Reference/Float). These approximations lead to large errors in the final distance value.
  2. How can I obtain a fast and precise position update during my path? If I walk along a direction I'm no able to retrieve a fast enough position update in terms of lat/lon coordinates.

Hope to be as much clear as possible and to have explained my problem, thanks in advance to anyone will give me any feedback or any information and please be patience for my bad english.

Filippo

[UPDATE]

@EdgarBonet that's exactly what I was looking for!!! Huge thanks!

Does the simple GPS library you linked is good to me also if my Flora board mounts an AtMega32u4 processor instead of the ATtiny85 you mentioned?

Last question: do you know how faster can I retrieve a GPS fix? I mean, if I am in a certain position (say X) and I am able to correctly obtain my GPS coordinates, then I move 5 meters ahead (say X+5), when I arrive at X+5 position can I retrieve within few seconds the X+5 GPS coordinates?

Thank you so much again @EdgarBonet!!

I'm currently working on a project involving making a "parent-child distance warning system" with my Flora board + Ultimate GPS Module (https://www.adafruit.com/product/1059). I'm on the preliminary phase and just trying to retrieve the current GPS module position, comparing it with a pre-fixed position (expressed in GPS decimal coordinates, e.g. 40.779006, -74.289395), calculate the distance between these two points and, if the distance is more than a certain value, say 20 meters, print on serial monitor a warning message. I was able to correctly retrieve the GPS module position (expressed in decimal coordinates, as above), but when I try to calculate the distance I'm not able to achieve a few-meter accurancy.

So, my questions are:

  1. Is there a specific/best formula to calculate short distances (max 100 meters) on the earth surface with an accurancy of 2-3 meters? I tried with Haversine formula with not so bad results, but because of the scarce Double precision (in ATMega processors is the same of the Float one, i.e. 32 bits), I'm able to obtain a precision of only 6-7 digits in total (as stated in https://www.arduino.cc/en/Reference/Float). These approximations lead to large errors in the final distance value.
  2. How can I obtain a fast and precise position update during my path? If I walk along a direction I'm no able to retrieve a fast enough position update in terms of lat/lon coordinates.

Hope to be as much clear as possible and to have explained my problem, thanks in advance to anyone will give me any feedback or any information and please be patience for my bad english.

Filippo

[UPDATE]

@EdgarBonet that's exactly what I was looking for!!! Huge thanks!

Does the simple GPS library you linked is good to me also if my Flora board mounts an AtMega32u4 processor instead of the ATtiny85 you mentioned?

Last question: do you know how faster can I retrieve a GPS fix? I mean, if I am in a certain position (say X) and I am able to correctly obtain my GPS coordinates, then I move 5 meters ahead (say X+5), when I arrive at X+5 position can I retrieve within few seconds the X+5 GPS coordinates?

Thank you so much again @EdgarBonet!!

[UPDATE 2]

The DistanceBetween method recommended to me by @EdgarBonet returns to me always 0, even if I move my self a couple of tens of meters.

But, maybe I am wrong in passing the parameters. Do I have to pass the lat/lon parameters as decimal coordinates (e.g: 45.892829, 12.082583)? If I am wrong, in which format do I have to pass the lat/lon parameters to the DistanceBetween method?

Greenonline
  • 3,152
  • 7
  • 36
  • 48
Filippo
  • 71
  • 1
  • 10

5 Answers5

3

The Haversine formula is way overkill for your needs. At the scale you are concerned about, the Earth is essentially flat, so the distance between two points is given by the standard Cartesian formula:

d = √(Δx2 + Δy2)

where Δx and Δy are the distances along the west–east and south–north axes respectively. These can be computed from the differences in latitude and longitude as

Δx = R Δλ cos(φ)
Δy = R Δφ

where φ is the latitude, λ is the longitude (both in radians), and R is the Earth radius. The term cos(φ) accounts for the fact that, far from the equator, consecutive meridians are closer together than consecutive parallels. When computing this cosine, it does not matter whether you use the latitude of either point.

I suggest you take a look at this simple GPS library. It was designed for doing exactly this kind of simple calculations on a small AVR processor like the ATtiny85. It should work great on your Flora. The function you want is DistanceBetween(). It uses the “locally flat Earth” approximation above, which is only good for distances up to a few hundred kilometers.

A couple of things worth noting about this library:

  • the library trades accuracy for efficiency: everything is fixed-point and it uses low-order polynomial approximations for cos(x) and √(x2 + y2)
  • angles are given in units of 10−4 arc minutes, as this is what you usually get from an NMEA sentence
  • DistanceBetween() reports the distance as an integer number of meters, but you can easily modify it to give it in units of half-meters, quarter-meters, etc.: just replace the value 512 in the last line (return ... / 512;) by a smaller power of two.
Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
2

I have successfully used an Arduino + hobby GPS to get an accuracy within a few meters with an update every second using normal floating point on Arduino. This should be possible.

A 32-bit float has 24 bits of precision, and one 2^24th of half the Earth's circumference is about 1.2 meters, so the theoretical cap is roughly that.

If I recall correctly, you would get better accuracy for short distances on a 32-bit float using normal 2D trig than haversine, but both should work.

Check to make sure you are reading your GPS correctly and go over the equations again. Is everything converted to radians correctly for the trig? Make sure your version of PI has enough digits. Are you correctly parsing the GPS output value as degrees+decimal minutes? It is not a simple decimal degree number (like Google Maps coordinates would be) if it came from an NMEA message. Are there any configuration startup commands you need to send your GPS unit?

I think the fastest updates you can expect is a new coordinate every second, but I haven't used your specific GPS.

BrettFolkins
  • 4,441
  • 1
  • 15
  • 26
0

The position difference calculation part of the question has been handled by Edgar. Which leaves the heading to the child part.

To work out which direction the parent is walking in don't use change in position. Use the heading and velocity outputs from the GPS. These will be far more accurate than calculating the velocity based on the change in position.

As for rate of update it depends a lot on the GPS being used, the one you linked to indicates that it supports 10 Hz updates so you should be able to get a position and heading update 10 times per second. The default will be 1 Hz so you'll need to send the correct configuration command. This will however significantly increase battery usage.

Those low end GPS systems typically have a fair amount of latency but the values you get in the serial stream shouldn't be more than 100 ms old, easily good enough for something as slow as a person walking.

Andrew
  • 1,060
  • 5
  • 8
0

You may be interested in my NeoGPS library. It is smaller, faster, more reliable and more accurate than all other libraries. It preserves the full accuracy of the GPS locations (10 significant digits), even during distance and bearing calculations. NeoGPS uses the equirectangular simplification suggested by Edgar at small distances, and switches to Haversine at large distances. As you have discovered, naive floating-point calculations (like those in all other libraries) lose that accuracy at small distances.

If you want to try it, NeoGPS is available from the Arduino IDE Library Manager, under the menu Sketch -> Include Library -> Manage Libraries.

slash-dev
  • 2,029
  • 13
  • 12
0

I have created Code to Calculate GPS Distances between Multiple Points on an Arduino Mega 2560 to 0.04 foot accuracy. I included in the Code "Functions" and Code to I used to keep track of Locations. They are not applicable to the Calculations but shows that you can bring in Points for both Distance from Rover To Base and Distance from Rover to next Point you are wanting to go to. It also calculates the Angles between the Base & Rover and Rover to Next Point. The angles are not Global but in reference to each of the points. I could not get Global Angles calculates to any precision that why I used Angles calculation between point. Serial.Print can substituted for lcd.print. From Zed-f9d UART1 Protocol Out ( 0+1 - UBX+NMEA) Bob Brandt, Hartsburg MO. Will Also Work with C94-M8P UART1 Protocol Out Calculates Feet to the nearest 0.04 Feet Distance Between Points Angles are Calculated with Reference to Base NOT Global Degrees Whole Numbers NOT used for LongDiff but are here if Needed Example Below. LongDiff = (( (88 * Secs2)- (88 * Secs1)) + ((5280 * Mins2)-(5280 * Mins1)));

Bob Brandt, Hartsburg MO.

    gpsData = "";                            
    while (Serial2.available()) {                       
    inChar = Serial2.read();                              
    gpsData += inChar;                                   
    if (inChar == '$') {                                         
    gpsData = Serial2.readStringUntil('\n');  
    }  }  
  String sGPRMC;
  sGPRMC = gpsData.substring(0, 5);
  if (sGPRMC == "GNRMC") { 
    String  LattBase =    "3840.xxxx1";          
    String  LongBase = "09214.8xxx4";       
    float   Angles_and_TotalFeet;
    float   LattDiff;
    float   LongDiff;
    int       Mins1;
    int       Mins2;      
    int        Deg;
    float    Secs1;
    float    Secs2;      
    float    LatRovSecs;  
    float    LonRovSecs;        
    String  a1st;  
    int     Ft_Per_Degree = 316800;    
   LattRover = gpsData.substring(18, 28);
   LongRover = gpsData.substring(31, 42);    
   int NS_POS = gpsData.substring(10, 35).indexOf('N');      // NS_POS  = North South Position Also Lattitude East West.
   lcd.setCursor(9, 2);
   lcd.print(LongRover); 
  lcd.setCursor(10, 3);    
  lcd.print(LattRover);    
  if( Func == 0){    
  if(Cutting_Phase == 2 &&  RunStart == false ){    
     lcd.setCursor(5, 0);    
  lcd.print("Next          "); }       
  if( Cutting_Phase < 2 &&  RunStart  == false){    
     lcd.setCursor(5, 0);    
  lcd.print("Ready          "); }
  lcd.setCursor(12, 1);    
  lcd.print("Location");  }  
   if( Func == 1){
  lcd.setCursor(5, 0);    
  lcd.print("Save Points");    
  lcd.setCursor(12, 1);    
  lcd.print("Location");  }
  lcd.setCursor(18, 0);    
  lcd.print(Cutting_Phase);    
  NS_POS = LongBase.indexOf('.');
  a1st   = String(LongBase.substring(0,NS_POS ).toInt());
  Deg = (a1st.substring(0, a1st.length()-2 )).toInt(); 
  Mins1 = (a1st.substring(a1st.length()-2 )).toInt(); 
  LastWord = a1st.substring(a1st.length(),2);   
  Mins1 = LastWord.toInt();  
  Secs1  = (LongBase.substring(NS_POS)).toFloat();     
  Secs1 = Secs1 * 60; 
  Ft_Per_Degree = 31680;    
  NS_POS = LongRover.indexOf('.');
  a1st   = String(LongRover.substring(0,NS_POS).toInt());
  Deg = (a1st.substring(0, a1st.length()-2 )).toInt();
  LastWord = a1st.substring(a1st.length(),2);   
  Mins2 = LastWord.toInt(); 
  Secs2  = (LongRover.substring(NS_POS)).toFloat();   
  Secs2 = Secs2 * 60; 
  LonRovSecs = Secs2;
  LongDiff = (( (88 * Secs2)- (88 * Secs1)) + ((5280 * Mins2)-(5280 * Mins1)));  
  Ft_Per_Degree =  sin(Deg * (PI/180)) * 316800;   
  NS_POS = LattBase.indexOf('.');
  a1st   = String(LattBase.substring(0,NS_POS ));
  Deg = (a1st.substring(0, a1st.length()-2)).toInt();    
  LastWord = a1st.substring(a1st.length(),2);   
  Mins1 = LastWord.toInt();      
  Secs1  =(LattBase.substring(NS_POS)).toFloat(); 
  Secs1 = Secs1 * 60;     
  NS_POS = LattRover.indexOf('.');
  a1st   = String(LattRover.substring(0,NS_POS ).toInt());
  Deg = (a1st.substring(0, a1st.length()-2)).toInt();    
  LastWord = a1st.substring(a1st.length(),2);   
  Mins2 = LastWord.toInt();     
  Secs2  = (LattRover.substring(NS_POS)).toFloat(); 
  Secs2 = Secs2 * 60; 
   LatRovSecs = Secs2;
  LattDiff  = (((88 * Secs2)- (88 * Secs1)) + ((5280 * Mins2)- (5280 * Mins1)));  
  Angles_and_TotalFeet = sqrt(sq(LongDiff) + sq(LattDiff));
  lcd.setCursor(0, 2);    
  lcd.print(Angles_and_TotalFeet  + String(" Ft  ")); 
  lcd.setCursor(19, 0);   
  if (LattDiff > 0 &&  LongDiff > 0) {
   Intermead = 270 + atan((LongDiff) / (LattDiff)) * 57.29578;
   lcd.print("1");   }
  if ( LongDiff < 0 && LattDiff < 0) {
  Intermead =  90 + atan(( LongDiff ) / (LattDiff )) *  57.29578;
  lcd.print("2");  }
  if  (LongDiff  < 0 && LattDiff > 0) {
  Intermead  =  (atan((LongDiff) / (LattDiff)) * 57.29578) * -1;
   lcd.print("3");  }
  if  (LongDiff  > 0 && LattDiff < 0) {
  Intermead  = 270 + (atan((LongDiff) / (LattDiff)) * 57.29578);
   lcd.print("4");  }
  lcd.setCursor(0, 3);   
  lcd.print("          "); 
  lcd.setCursor(0, 3);    
  lcd.print(Intermead + String(" Dg")); 
 int value = (SavedLongitude[0]);    //Location to Go To.
 if ( value > 1   &&     RunStart) {       // RunStart tell program to Start Moving
  lcd.setCursor(0, 1);
  lcd.print("          ");
  lcd.setCursor(0, 1);
  lcd.print(YardLocation);                //Name of File to get Info
  LongBase = "";
  LattBase = "";
  LongBase  = SavedLongitude;   
  NS_POS = LongBase.indexOf('.');
  a1st   = String(LongBase.substring(0,NS_POS ).toInt());
  Deg = (a1st.substring(0, a1st.length()-2 )).toInt(); 
  Mins1 = (a1st.substring(a1st.length()-2 )).toInt();  
  Secs1  = (LongBase.substring(NS_POS)).toFloat();   
  Secs1 = Secs1 * 60;    
  LongDiff = (((88 * LonRovSecs)- (88 * Secs1)));
  LattBase  = SavedLatitude;   
  NS_POS = LattBase.indexOf('.');
  a1st   = String(LattBase.substring(0,NS_POS ));
  Deg = (a1st.substring(0, a1st.length()-2)).toInt();  
  Mins1 = (a1st.substring(a1st.length(),2 )).toInt();   
  Secs1  =(LattBase.substring(NS_POS)).toFloat(); 
  Secs1 = Secs1 * 60; 
  LattDiff = (((88 * LatRovSecs )- (88 * Secs1)));   
  Angles_and_TotalFeet = sqrt(sq(LongDiff) + sq(LattDiff));
   lcd.setCursor(4, 0);   
  lcd.print("        ");   
  lcd.setCursor(5, 0);   
  lcd.print(Angles_and_TotalFeet + String("'") ); // Rover and next point distance apart

lcd.setCursor(19, 0);
if (LattDiff > 0 && LongDiff > 0) { Intermead = 270 + atan((LongDiff) / (LattDiff)) * 57.29578; lcd.print("1"); } if ( LongDiff < 0 && LattDiff < 0) { Intermead = 90 + atan(( LongDiff ) / (LattDiff )) * 57.29578; lcd.print("2"); } if (LongDiff < 0 && LattDiff > 0) { Intermead = (atan((LongDiff) / (LattDiff)) * 57.29578) * -1; lcd.print("3"); } if (LongDiff > 0 && LattDiff < 0) { Intermead = 270 + (atan((LongDiff) / (LattDiff)) * 57.29578); lcd.print("4"); } Current_Heading = Intermead; if ( Cutting_Phase == 1 && (Intermead > (headingDegrees - 1)) && (Intermead < (headingDegrees + 1))) { Cutting_Phase = 2;
RunStart = false; }

lcd.setCursor(11, 0);
lcd.print(Intermead + String("D"));
delay(10); } else { lcd.setCursor(0, 1); lcd.print(" ");
lcd.setCursor(0, 1); lcd.print(YardLocation); }

} }

Gil Sven
  • 167
  • 8
user66377
  • 101