Storing latitute and longitude using a double

Dear colleagues,

I’m working on a project that is centered around a GPS NEO 6 Module.

The idea is simple - a LED should turn on in case the gps module is in a predefined square on the map.

The code shouldn’t be tricky, but I’m already stuck.

I’m using the TinyGPS++.h library, and this is the important method/function:

gps.location.lat(), 6

When printed, it’s tells me the latitude, with 6 decimal points precision.

Here comes the problem - I want to store that data, so that I can compare it with the corner points of the square.

Serial.print(gps.location.lat(), 6) and I get something like 20.915727 which is correct.

The code below is from the library explanation file :

Serial.println(gps.location.lat(), 6); // Latitude in degrees (double)

Number 6 is the number of decimal places, that number can be changed.

How do I store a number with so many decimal places ?

I tried saving it a a double, but the saved number is = 06.00 :o :’(

Any guess ?

#include <TinyGPS++.h>
#include <SoftwareSerial.h>

TinyGPSPlus gps; 
SoftwareSerial gpsPort(4, 5); 

long long period = millis();
int interval = 4000;

void gpsTelemetrija(){
  if (millis() - period > interval ) 
  {
    while (gpsPort.available() > 0) {   
      gps.encode(gpsPort.read());       
      if (gps.location.isUpdated()) {    
        Serial.print("Latitute = ");
        Serial.print(gps.location.lat(), 6);  
       // double latitudeData = (gps.location.lat(), 6);       - this variable will be 06.00 instead of 20.123456 :/
        Serial.print(" N"); 
        Serial.print("| Meridian = ");
        Serial.print(gps.location.lng(), 6); 
        Serial.println(" E"); 
     
        Serial.println("---------"); 
        period = millis();
      }
    }
  }
  }
  
void setup() {
  Serial.begin(9600); 
  gpsPort.begin(9600);
}

Summa Sumarum - how do I save the latitude/longitude number that the method spits, and compare it to some other value ?
p.s.
TinyGPS++ | Arduiniana - library info

Thanks <3

double and float are the same implementation on the AVRs.

I doubt that the GPS NEO 6 spits the values out as either, though - why not leave them as strings?

Dacha011:
How do I store a number with so many decimal places ?

float savedlat;

savedlat = gps.location.lat();

A float variable takes 4 bytes of storage, regardless of how many decimal places you choose to print.

How do I store a number with so many decimal places ?

A float or double is a binary value and does not have a well defined number of decimal places.

"Decimal places" have meaning only when you print the number.

To store latitude and longitude values and maintain full accuracy, store either the text string as received from the GPS unit, or store the long integer values maintained internally by TinyGPS. You will lose accuracy if you save the float value.

double latitudeData = (gps.location.lat(), 6);

The Comma Operator strikes again. Very bad, don't use it.

gfvalvo:

double latitudeData = (gps.location.lat(), 6);

The Comma Operator strikes again. Very bad, don’t use it.

Yep, this confusion starts when someone sees the code…

Serial.print(gps.location.lat(), 6);

…and doesn’t understand that the 6 is just another parameter to Serial.print which tells it to format the supplied double to 6 DPs.

But the syntax…

double latitudeData = (gps.location.lat(), 6);

…has a completely different meaning when used outside of the context of the Serial.print function.

If you need to compare a float/double is within a particular range…

if (abs(currentLat - pointLat) <= 0.0001)

But note that float only has 6 or 7 digits of precision on Arduino, and for the vast majority of boards (with a few exceptions, e.g. Arduino Due) doubles have exactly the same precision as floats.

Edit: Note 6 digits of precision equates to about 40m when using GPS co-ordinates.

pcbbc:
Edit: Note 6 digits of precision equates to about 40m when using GPS co-ordinates.

OP could use the raw lat/lng values available from TinyGPS++ for better precision.

gfvalvo:
OP could use the raw lat/lng values available from TinyGPS++ for better precision.

Indeed, but then not so easy (although not impossible) to tell if the value is within a particular range.
Note the OPs original requirement...

Here comes the problem - I want to store that data, so that I can compare it with the corner points of the square

then not so easy (although not impossible) to tell if the value is within a particular range

It is quite simple, as a matter of fact. The coordinates are stored by TinyGPS++ as long integers, in decimal degrees*10^7. So, you can determine whether a given (lat, lon) point is within a rectangular or circular region with a single, well chosen if statement.

For geofencing applications there are available routines that will do the comparison for an arbitrary polygon fence.

@TheMemberFormerlyKnownAsAWOL - “why not leave them as strings ?”

In case I leave it as a string , how will I compare the lat/long strings with other coordinates, meaning - it’s going to be easy to compare the lat/long string with a certan point (x,y coordinates) on the map, but what If I want to make a statement like:

if 20.125645 < lat/long string < 20.897456 ?

It’s not hard to get the relevant $GPGGA string using .readStringUntil(), but it is a String object.
How do I convert the parts after the 2nd and 4th comma ?

$GPGGA,100401.00,4248.43237,N,02131.14561,E,1,08,1.24,157.0,M,36.7,M,*5A

@jremington - do I need to store lat and long for comparison ?
Maybe I should compare lat/long with a string with 4 or 5 DPs, so that there is an acceptable error margin ?

@pcbbc

if (abs(currentLat - pointLat) <= 0.0001)

Great idea. But this way requires the lat/long to be stored as float/double. 6 DP are responsible for 40m accuracy :slight_smile: Nice info ^^

@gfvalvo - raw lat,long. I saw it in the library explanation file, I’ll check it tomorrow.

The coordinates are stored by TinyGPS++ as long integers, in decimal degrees*10^7. So, you can determine whether a given (lat, lon) point is within a rectangular or circular region with a single, well chosen if statement.

Nice advice !

Thanks guys <3, tomorrow I will resume the work ^^

Before you go any further,

if 20.125645 < lat/long string < 20.897456 ?

or maybe you would be smart enough to:

if  (20.125645 < lstring < 20.897456) {...

but that is not valid C syntax. You have to do separate comparisons:

if  (20.125645 < lstring and lstring < 20.897456) {...

do I need to store lat and long for comparison

You need both to define where you are, and for a rectangular region, two limits each on lat and lon to determine whether you are within a defined area.

Maybe I should compare lat/long with a string with 4 or 5 DPs

NO

It is much easier to compare latitude and longitude coordinates in long integer format, in decimal degrees*10000000. They are just numbers that define a position to within a few cm.

if (lat >= latmin && lat < latmax && lon >= lonmin && lon < lonmax) set_in_region_flag();

Dacha011:
@TheMemberFormerlyKnownAsAWOL - “why not leave them as strings ?”

In case I leave it as a string , how will I compare the lat/long strings with other coordinates, meaning - it’s going to be easy to compare the lat/long string with a certan point (x,y coordinates) on the map, but what If I want to make a statement like:

Have you tried leaving them as strings, or have you just decided that’s not going to work?

Edit: strings will work just as well as integer variables for “squares” orthogonal to the axes.

Ok guys, there is some progress.
Please check the part under the comment “//the code below is just me experimenting”

void gpsTelemetrija(){
  if (millis() - period > interval )  // 
  {
    while (gpsPort.available() > 0) {  
      gps.encode(gpsPort.read());       // metod koji salje podatke iz modula u TinyGPS
      if (gps.location.isUpdated()) {    
        
        Serial.print("Latitude: ");
        Serial.print(gps.location.lat(), 6);     //Good data is printed
        Serial.print("  Longitude:");
        Serial.println(gps.location.lng(), 6);   //Good data is printed
                
        Serial.print("Degrees : ");
        Serial.println(gps.location.rawLat().deg);  // I get 20 which is ok. 

        Serial.print("Billionths: ");
        Serial.println(gps.location.rawLat().billionths); //I get 807215667, also ok

        //the code below is just me experimenting
        long int  lat1 = 20.123456;
        float  lat2 = 20.123456;
        Serial.println(lat1);    // 20 is printed
        Serial.println(lat2,6);  //20.123455 is printed, the 6th digit is off
         
                    
    
        period = millis();
      }
    }
  }
  }

Is there a data type, except string, that will precisely save a number like 50.123456 ?

The raw lat/lng method is providing me with good info.
But I need to use 2 long int numbers - one for degrees, the other for billionths.

So - do I have to compare in the first step the module degrees with the desired location degrees, and than if that’s True, in the second step I will compare the module billionths with the desired location billionths (see if the difference is acceptable) ?

The logic from the sentence above seams easy, but I’m hoping I can compare in one step both degrees and bilionths, as aarg said :

if  (20.125645 < lstring and lstring < 20.897456) {...

@jremington - this also looks neat

if (lat >= latmin && lat < latmax && lon >= lonmin && lon < lonmax) set_in_region_flag();

Edit: strings will work just as well as integer variables for “squares” orthogonal to the axes.

I will check that (I do believe you) - that could be the easiest solution to my problem.
Just comparing strings (that look like a number with 6 DPs) like regular numbers.

Dacha011:
The idea is simple - a LED should turn on in case the gps module is in a predefined square on the map.

What resolution of position do you want ?

The fifth decimal place, if a float is used to measure latitude, represents units of 1.1m.

Then remember that a GPS is going to give a position accurate to 3-10m only.

Dacha011:
Is there a data type, except string, that will precisely save a number like 50.123456 ?

Yes, it's called a scaled integer, or "fixed point". Basically you just store the number multiplied by a constant scale. In your calculations, you take into account the scale. So you store 50.123456 as the integer 50123456. Here the scale factor is decimal 1000000.

To convert TinyGPS++ raw latitudes and longitudes, as stored in their internal form, to the extremely convenient long integer format in degrees*(ten million), use something like:

long scale=10000000UL;
long lat = gps.location.rawLat().deg*scale+gps.location.rawLat().billionths/100UL;
if(gps.location.rawLat().negative) lat=-lat;
long lon = gps.location.rawLng().deg*scale+gps.location.rawLng().billionths/100UL;
if(gps.location.rawLng().negative) lon=-lon;

With this scale factor, 45.123456 degrees is represented internally by 451234560 and specifies location to +/- 1 cm precision.

Note this assumes that the GPS object is named "gps".

@jermington - HUGE THANK YOU !

I integrated the code you wrote in my code, and now it works perfectly.
But a bigger THANK YOU because I have learned something new, you taught me a new concept, and you explained it to me with a code.

@aarg - thanks mate for explaining me the scaled integer concept.

@srnet - 10-15m is more than ok ^^

Aanyway - I made 3-4 test rectangles on the map in my neighborhood, and the Nano told me when I was in each of them.

<3

Glad to hear it works to your satisfaction!

If you would be so kind as to post a simple, working geofence example, that may help others who are getting started.

Dear colleagues,

Here is my geofencing code, with some explanation.

//GEOFENCING by Dacha011
// Works only north of the equator, and West of the prime meridian. (only positive GPS coordinates)

#include <TinyGPS++.h>
#include <SoftwareSerial.h>

TinyGPSPlus gps; //GPS Object
SoftwareSerial SerialPort(3, 2);

long latitude;      //Latitude (North - South)
long longitude;     //Longitude (East - West)

long scale = 10000000UL;     //10 milion. Why ? This technique is called - integer scaling. Please see below

long long timer = millis(); //timer

unsigned int interval = 4000; //interval

void setup() {
  Serial.begin(9600);
  SerialPort.begin(9600);
  pinMode(7,OUTPUT); //buzzer
}

void loop() {
  gpsTelemetrija();
}

void gpsTelemetrija() {     // this function will take GPS data from the module, and compare it with some points on the map that I predefined
  if (millis() - timer > interval )  // check once in 4 sec
  {
    while (SerialPort.available() > 0) {   //
      gps.encode(SerialPort.read());       //
      if (gps.location.isUpdated()) {    //


        latitude = gps.location.rawLat().deg * scale + gps.location.rawLat().billionths / 100UL;
        longitude = gps.location.rawLng().deg * scale + gps.location.rawLng().billionths / 100UL;

        /*
          The GPS Module is givin you raw LAT and LNG data. LAT and LNG are given in degrees and billionths, as two separate values.
          The raw data won't be given to you in a decimal format such as 50.123456, but reather as 50 degrees and 1234567895 billionths.
          You want to store LAT (or LGN) in one variable (and not two, the virst variable for degrees, the other one for billionts).
          If you save it as a float number, it will be preceise only up to 5 decimal places.
          So here comes the important trick - you will store all the degrees and billionth values in a long data type variable.
          Degrees will be multiplied with the variable scale(10 milion) - e.g. 30 degrees N * 10000000UL = 300000000  (9 digits)
          Billionths will be devided by 100UL. Why ? To get a 7 digits number, that will be added to the 9 digits degree value
          (You want to leave alone the first 2 digits of your degrees value. Billionths should begin from the third digit)
          This technique is called integer scaleing.

          note: UL should be added at the end of every long data type value. i.e.  long = 12657854UL;
        */



        Serial.println(" -  -  -  -  - ");
        Serial.print("Current coordinates: ");
        Serial.print (latitude);
        Serial.print (" - ");
        Serial.println (longitude);
        timer = millis(); //reset timer 

        if (compare(448073075UL, 448068685UL, 205191775UL, 205187295UL, latitude, longitude, " Parking ") == true) { 
          zvono(1); //beep once

        }
        else if (compare(448096755UL, 448093755UL, 205156795UL, 205153305UL, latitude, longitude, "  Church ") == true) { 
          zvono(2);

        }

        else if (compare(448087235UL, 448082105UL, 205135965UL, 205126365UL, latitude, longitude, " Gas pump") == true) { 
          zvono(3);

        }
        else if (compare(448040065UL, 448036235UL, 205205835UL, 205200315UL, latitude, longitude, " Crossroad ") == true) { 
          zvono(4); //beep 4 times

        }
        else
          Serial.println("Not in a predefined zone");
      }
    }
  }
}



bool compare (long Latmax, long Latmin, long Lngmax, long Lngmin, long latit, long longitude, String place) { //Analyze if the GPS module is within the predefined boundries 
/* Let's say you want to know when your gps module is near a supermarket.  
   Open a map on your browser and define a rectangle/square around your target. Save the next 4 values of your square/rectangle:
   Upper left corner LAT and LGN   (Latmax and Lngmin)
   Bottom left corner LAT (Latmin)
   Upper right corner LGN (Lngmax)
   IMPORTANT NOTE: Latmin/max and Lngminmax must have 9 digits. So add the 9th digit, in case your browser map gives you only 8 numbers.
   i.e. 57.123456 N   will be 571234565UL (I added 5 at the end, and then UL)
   
*/
  if ((latitude > Latmin) and (latitude < Latmax)) {          //if latitude is between lat max and lat min
    if ((longitude > Lngmin) and (longitude < Lngmax) ) {     //if yes, check longitude
      Serial.print("Location: ");                              //if both lat and lgn are within the zone - BINGO ! Print the name of the zone
      Serial.print(place);
      return true; 
    }
  }
  else {
           // zero
    return 0;
  }
  Serial.println (" ----------------- ");
}

void zvono (int x) {  //buzzer. The argument is the number of time the buzzer should ring
  for (int a = 0; a < x; a++) {
    tone(7, 22, 30);
    delay(200);

  }
}

It works perfectly. The buzzer beeps as soon as I enter in the predefined zone.

A few observations :

  1. I’m sure there are simpler and more elegant geofencing codes around. I wrote this one, and it works ok for me.

  2. This code is probably an overkill because it’s storing 7 digital places (0.11 m precision, while the NEO 6 module has an error of 5-10+m in ideal conditions), but at least you will learn the integer scaling method :slight_smile: You might need it tomorrow

  3. It’s possible to store the lat and lgn details as a float (5DPs will give you 1m precision), but I decided to go for a more precise solution.

  4. LAT and LGN can also be stored as strings, and then those strings can be compared, but I wanted to explore the code @jremington wrote me.

  5. I’m certain someone who’s good with math can write a function that has 3 arguments - the LAT and LNG of a point, and a DIAMETER, so that the geofenced zone will be a circle, and you just have to define the center point and diameter.
    I’m not that good with math :’(

Fell free to criticize my code, or suggest some improvement (that will not radical change my code) :slight_smile:

Thanks again guys, without you the path to the solution would be miles longer.

Cheers ! <3