GPS Parser and Distance Finding

This is a small, compact library used for parsing GPS GPMRC data written in C. The library is only one header file, it will parse the NEMA strings and put them into user readable data. Unit conversion is done within the library functions to get information such as:

    Latitude and Longitude
    current time (UTC)
    date
    heading (both degrees and letter direction)
    ground speed (knots, miles per hour, and feet per second)

There is also a function that uses the Harversine formula to determine the distance between two points on the planet. This formula takes into account the earth being an ellipsoid. It will give you distance in Kilometers, miles, and feet.

Below is a small sample program that shows how to parse a GPS NEMA strings. It will also show how to set your current location and a target location and how to use the library to calculate the distance between two points.

struct Packet loc;
struct Position curPos, target;

The first two lines are used to set up the structures that will be used to hold the parsed data as well as the two positions that we will find the distance between.

clearPacket(&loc);
char buffer[256]= {'\0'};
	
fgets(buffer, sizeof(buffer), stdin);

The first function will do what it says, clear the packet structure so that all values are \0. the Buffer will hold the string that is taken from stdin.

if(parseGPS(buffer, &loc)>=0) //the parser will return -1 if the packet is not a full and valid GPS packet. 
{
	printData(&loc);
}
else
{
	printf("not a valid packet\n");
}

This is where all the processing is done. We feed the buffer and the address to the packet structure. The parser function will go through the string and place its contents into the correct location within the structure. The parser will return -1 if there is not a real or valid packet. If a packet is showing correct latitude and longitude but no other values then it is probably a GPGGA string which have the same validation byte, but not the same formating…hence why only part of the data is shown. The printData function will print the data into a neatly formated human readable form. The longitude and latitude are converted to decimal degrees which can be used for things like google maps or other applications that accept lat and lon in decimal degrees.

/////////////////////////////////////////////////////////
//using distance measuring
//set the current position
setPosition(&curPos, loc.lat, loc.lon);

//set the target position using a known coordinate 
setPosition(&target, 38.897701, -77.036637);

here we set two positions. The first set of coordinates are taken from the structure holding all of our current GPS data. The second position is the target, this can be hardcoded into your program, like it is here, or any other way that you want to store them.

//print distance in KM, miles, and feet
	int dist = getDistanceKM(&curPos, &target);
	printf("distance KM:%d\n", dist);
	
	dist = getDistanceMiles(&curPos, &target);
	printf("distance Miles:%d\n", dist);
	
	dist = getDistanceFeet(&curPos, &target);
	printf("distance Feet:%d\n", dist);

once we have the two positions set we can pass them to one of the getDistance functions. There are three variations which will return an int in KM, Feet, or Miles. That is all! if you want to get altitude you can feel free to modify the code in the ParseGPS function and add another IF statement that will read in GPGAA packets. When compiling your code make sure to link the math library which is used for the trig functions sin, cos, acos, etc. Using GCC you could compile with

GCC -Wall -o gpsDecode gpsDecode.c -lm

Below are the two files in their entirety.
GPS_DECODE.c


#include "gpsDecode.h"


int main(void)
{

	
	struct Packet loc;
	struct Position curPos, target;
	

		
	clearPacket(&loc);
	char buffer[256]= {'\0'};
	
	fgets(buffer, sizeof(buffer), stdin);

	if(parseGPS(buffer, &loc)>=0) //the parser will return -1 if the packet is not a full and valid GPS packet. 
	{
		printData(&loc);
	}
	else
	{
		printf("not a valid packet\n");
	}
	
	/////////////////////////////////////////////////////////
	//using distance measuring
	//set the current position
	setPosition(&curPos, loc.lat, loc.lon);
	
	//set the target position using a known coordinate 
	setPosition(&target, 38.897701, -77.036637);
	printf("\n");
	
	//print distance in KM, miles, and feet
	int dist = getDistanceKM(&curPos, &target);
	printf("distance KM:%d\n", dist);
	
	dist = getDistanceMiles(&curPos, &target);
	printf("distance Miles:%d\n", dist);
	
	dist = getDistanceFeet(&curPos, &target);
	printf("distance Feet:%d\n", dist);


	return 0;


}
	



GPS_DECODE.h

//gpsDecoder.h
//decode GPS NEMA data
//This decoder is very lightweight and only parses data from GPRMC packets
//this offers the minimum ammount of data to be parsed and gets you everything but altitude. 
//if you need altitude feel free to modify the parseGPS routine and add an alternate IF statment for GPGGA Packets

//written by: Bill Heaster
//1/20/16
//This shit is free, do what you want with it, just give me some credit. 
//theCreator a.t ApexLogic d0t Net
/////////////////////////////////////////////////////////////////


#include <stdio.h>
#include <math.h> //for harversine sin, cos, acos, etc. 
#include <stdlib.h>
#include <string.h>



struct Packet
{
	
	//decimal degree format for use in mapping software. ie. google maps 
	float lat;
	float lon;
	
	//latitude and longitude
	int latDegree;
	double latMinSec;
	
	int lonDegree;
	double lonMinSec;
	
	//time
	int timeHours;
	int timeMinutes;
	int timeSeconds;
	
	//date
	int day;
	int month;
	int year;
	
	//hemisphere
	char latHem;
	char lonHem;
	
	//altitude
	int alt;
	
	//number of fixed satalites
	int numSats;
	
	//movement

	float heading;
	char headingDirection[3];
	
	float speedKnots;
	float speedFPS;
	float speedMPH;
	
	int isValid;
	int isLatNeg;
	int isLonNeg;
	
};

struct Position
{
	float lat;
	float lon;	
	float latRad;
	float lonRad;
};

int getDistanceKM(struct Position * point1, struct Position * point2)
{
	
	//sanity check
	if(point1->lat<0)
		point1->lat*=-1;
	if(point1->lon<0)
		point1->lon*=-1;
	if(point2->lat<0)
		point2->lat*=-1;
	if(point2->lon<0)
		point2->lon*=-1;

	
	
	//convert decimal degrees to radians
	point1->latRad = point1->lat * 0.017453293;
	point1->lonRad = point1->lon * 0.017453293;
	point2->latRad = point2->lat * 0.017453293;
	point2->lonRad = point2->lon * 0.017453293;
	
	//find the difference values between lat and lon radians
	float diffLat, diffLon = 0;
	
	if(point1->latRad > point2->latRad)
	{
		diffLat = point1->latRad - point2->latRad;
	}
	else
	{
		diffLat = point2->latRad - point1->latRad;
	}
	if(point1->lonRad > point2->lonRad)
	{
		diffLon = point1->lonRad - point2 ->lonRad;
	}
	else
	{
		diffLon = point2 ->lonRad - point1->lonRad;
	}
	
	

	
	float latSin = sin(diffLat/2);
	float lonSin = sin(diffLon/2);
	
	float distanceKM = 2* asin(sqrt((latSin*latSin)+cos(point1->latRad) *cos(point2->latRad) * (lonSin *lonSin)));
	distanceKM *= 6371;
	
	
		
	return (int)distanceKM;
	
}


int getDistanceMiles(struct Position * point1, struct Position * point2)
{
	
	//sanity check
	if(point1->lat<0)
		point1->lat*=-1;
	if(point1->lon<0)
		point1->lon*=-1;
	if(point2->lat<0)
		point2->lat*=-1;
	if(point2->lon<0)
		point2->lon*=-1;

	
	
	//convert decimal degrees to radians
	point1->latRad = point1->lat * 0.017453293;
	point1->lonRad = point1->lon * 0.017453293;
	point2->latRad = point2->lat * 0.017453293;
	point2->lonRad = point2->lon * 0.017453293;
	
	//find the difference values between lat and lon radians
	float diffLat, diffLon = 0;
	
	if(point1->latRad > point2->latRad)
	{
		diffLat = point1->latRad - point2->latRad;
	}
	else
	{
		diffLat = point2->latRad - point1->latRad;
	}
	if(point1->lonRad > point2->lonRad)
	{
		diffLon = point1->lonRad - point2 ->lonRad;
	}
	else
	{
		diffLon = point2 ->lonRad - point1->lonRad;
	}
	
	

	
	float latSin = sin(diffLat/2);
	float lonSin = sin(diffLon/2);
	
	float distanceKM = 2* asin(sqrt((latSin*latSin)+cos(point1->latRad) *cos(point2->latRad) * (lonSin *lonSin)));
	
	distanceKM *= 6371;
	
	distanceKM*= 0.621371192;
	
		
	return (int)distanceKM;
	
}

int getDistanceFeet(struct Position * point1, struct Position * point2)
{
	
	//sanity check
	if(point1->lat<0)
		point1->lat*=-1;
	if(point1->lon<0)
		point1->lon*=-1;
	if(point2->lat<0)
		point2->lat*=-1;
	if(point2->lon<0)
		point2->lon*=-1;

	
	
	//convert decimal degrees to radians
	point1->latRad = point1->lat * 0.017453293;
	point1->lonRad = point1->lon * 0.017453293;
	point2->latRad = point2->lat * 0.017453293;
	point2->lonRad = point2->lon * 0.017453293;
	
	//find the difference values between lat and lon radians
	float diffLat, diffLon = 0;
	
	if(point1->latRad > point2->latRad)
	{
		diffLat = point1->latRad - point2->latRad;
	}
	else
	{
		diffLat = point2->latRad - point1->latRad;
	}
	if(point1->lonRad > point2->lonRad)
	{
		diffLon = point1->lonRad - point2 ->lonRad;
	}
	else
	{
		diffLon = point2 ->lonRad - point1->lonRad;
	}
	
	

	
	float latSin = sin(diffLat/2);
	float lonSin = sin(diffLon/2);
	
	float distanceKM = 2* asin(sqrt((latSin*latSin)+cos(point1->latRad) *cos(point2->latRad) * (lonSin *lonSin)));
	distanceKM *= 6371;
	
	distanceKM*= 0.621371192;
	
	distanceKM *= 5282;
		
	return (int)distanceKM;
	
}

void clearPacket(struct Packet * loc)
{
	loc->latDegree = 0;
	loc->lonDegree =0;
	loc->latMinSec =0;
	loc->lonMinSec =0;
	loc->timeHours =0;
	loc->timeMinutes =0;
	loc->timeSeconds =0;
	loc->day =0;
	loc->month=0;
	loc->year=0;
	loc->latHem='\0';
	loc->lonHem='\0';
	loc->alt=0;
	loc->numSats=0;
	loc->heading=0;
	loc->speedFPS =0;
	loc->speedKnots =0;
	loc->speedMPH =0;
	loc->isValid=0;
	loc->isLatNeg = 0;
	loc->isLonNeg =0;
	bzero(loc->headingDirection, sizeof(loc->headingDirection));

	return;
}

//used to set the positions within the position structure. this must be done when using the distance measuring routines
void setPosition(struct Position *p, float lat, float lon)
{
	p->lat = lat;
	p->lon = lon;
	
	return;
}

float convertToDD(int degree, float minSec)
{
	float a = 0.00000;
	
	float temp = 0.00000;
	
	temp = minSec/60;
	
	a = degree + temp;
	
	
	
	return a;
	
}

//gps sends us data in knots, convert it
float knotsToMPH(float knots)
{
	
	return knots * 1.15077945;
	
	
	
}

//more percision if your module provides it
float mphToFPS(float mph)
{
	
	return mph * 1.46667;
	
}


//used for the heading when the compass goes from 0 to 360
int checkbounds(int num)
{
	if(num>360)
	{
		return num -=360;
	}
	
	if(num < 0)
	{
		return num +=360;
	}
	
	
	return num;
}

//adds threshold checking and turns the heading degrees into N, E, S , W, NE, NW, SE, SW headings
void setHeading(struct Packet * loc)
{
	int th= 15; //threshold in degrees
	int in = (int)loc->heading;	

	
	int north = 0;
	int east = 90;
	int south = 180;
	int west = 270;
	
	int nl = checkbounds(north-th);
	int nh = checkbounds(north+th);
	
	int el = checkbounds(east -th);
	int eh = checkbounds(east +th);
	
	int sl = checkbounds(south-th);
	int sh = checkbounds(south +th);
	
	int wl = checkbounds(west-th);
	int wh = checkbounds(west+th);
	
	if((in >=nl && in <=360)||(in>=0 && in<=nh))
	{
		loc->headingDirection[0] ='N';
		return;
	}
	if(in >=el && in <=eh)
	{
		loc->headingDirection[0] ='E';
		return;
	}
	if(in >=wl && in <=wh)
	{
		loc->headingDirection[0] ='W';
		return;
	}
	if(in >=sl && in <=sh)
	{
		loc->headingDirection[0] ='S';
		return;
	}
	
	if(in<el && in>nh)
	{
		loc->headingDirection[0] ='N';
		loc->headingDirection[1] ='E';
	}
	if(in>eh && in<sl)
	{
		loc->headingDirection[0] ='S';
		loc->headingDirection[1] ='E';
	}
	if(in>sh && in<wl)
	{
		loc->headingDirection[0] ='S';
		loc->headingDirection[1] ='W';
		return;
	}
	if(in>wh && in<nl)
	{
		loc->headingDirection[0] ='N';
		loc->headingDirection[1] ='W';
		return;
	}
	else
	loc->headingDirection[0] = 'u';
	
	return;
		
}

void printData(struct Packet * loc)
{

	
	printf("\n-------------------------------------------------------\n");
	printf("time:%d:%d:%d\t", loc->timeHours, loc->timeMinutes, loc->timeSeconds);
	printf("date:%d/%d/%d\n", loc->month, loc->day, loc->year);
	printf("latitude: %f %c\t",loc->lat, loc->latHem);
	printf("longitude: %f %c\n", loc->lon, loc->lonHem);
	
	
	
	loc->speedMPH= knotsToMPH(loc->speedKnots);
	loc->speedFPS = mphToFPS(loc->speedMPH);
	
	setHeading(loc);
	
	printf("knots:%f\tMPH:%f\tFPS:%f\n", loc->speedKnots, loc->speedMPH, loc->speedFPS);
	printf("heading:%f\t\tDirection:%s",loc->heading,loc->headingDirection);
	printf("\n-------------------------------------------------------\n");
	
	return;
}


int parseGPS(char * input, struct Packet * packet)
{
	
	char* currentLocation = input;
	
	char temp[128];
	bzero(temp, sizeof(temp));
	
	
	//check the string to see what kind of GPS packet it is. 
	//This module can only receive GPGSA, GPGGA, GPMRC 
	//All the data needed to fill the struct can be captured from the GPMRC packets
	//so we will process these, and drop all others. 
	//$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh
	
	
	char * needle = "$GPRMC";
	int len = strlen(needle);
	
	
	//if we have a valid GPMRC start code
	if(strstr(input, needle)!=NULL)
	{
		//put us on the first hour char
		currentLocation += (len+1);
		
		//next two are hours, place it in the struct
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->timeHours = atoi(temp);
		
		//get the minutes
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->timeMinutes = atoi(temp);
		
		//get seconds
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
				
		
		packet->timeSeconds = atoi(temp);
		bzero(temp, sizeof(temp));
		
		//current location on decimal point increment +5
		currentLocation+=5;
		
		//current location on packet status
		//if this isnt a valid packet clear the structure and return non-valid
		if(*currentLocation != 'A')
		{
			clearPacket(packet);
			return -1;
		}
		
		//increment +2 
		currentLocation +=2;
		
		//current location, first char of degree latitude
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->latDegree = atoi(temp);
		
		//current location is lat min first char
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		temp[2] = *currentLocation++;
		temp[3] = *currentLocation++;
		temp[4] = *currentLocation++;
		temp[5] = *currentLocation++;
		temp[6] = *currentLocation++;
		
		packet->latMinSec = atof(temp);
		bzero(temp, sizeof(temp));
		currentLocation++;
		
		//current location is N/S indicator
		packet->latHem = *currentLocation++;
		currentLocation++;
		
		//current location is first char degree lon
		if(*currentLocation == '0')
		{
			packet->isLonNeg = 1;
			currentLocation++;
			temp[0]=*currentLocation++;
			temp[1]=*currentLocation++;
			packet->lonDegree= atoi(temp);
			
		}
		else
		{
			temp[0] = *currentLocation++;	
			temp[1] = *currentLocation++;
			temp[2] = *currentLocation++;
			packet->lonDegree= atoi(temp);
		}
		

		
		//current location is first char of lon minutes
		
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		temp[2] = *currentLocation++;
		temp[3] = *currentLocation++;
		temp[4] = *currentLocation++;
		temp[5] = *currentLocation++;
		temp[6] = *currentLocation++;
		
		
		packet->lonMinSec= atof(temp);
		currentLocation++;
		
		//need to do this here i guess
		//convert coordinates into cartesian
		packet->lat = convertToDD(packet->latDegree, packet->latMinSec);
	
		packet->lon = convertToDD(packet->lonDegree, packet->lonMinSec);
		
		if(packet->isLonNeg)
		{
			packet->lon *= -1;
		}
		
		//current location is EW indicator
		packet->lonHem = *currentLocation++;
		currentLocation++;
		bzero(temp, sizeof(temp));
		
		//current location is speed in knots
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		temp[2] = *currentLocation++;
		temp[3] = *currentLocation++;
		
		packet->speedKnots= atof(temp);
		currentLocation++;
		bzero(temp, sizeof(temp));
		
		//current location is heading
		
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		temp[2] = *currentLocation++;
		temp[3] = *currentLocation++;
		temp[4] = *currentLocation++;
		temp[5] = *currentLocation++;
		
		packet->heading = atof(temp);
		currentLocation++;
		bzero(temp, sizeof(temp));
		
		//current location is day of date
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->day = atoi(temp);
		
		//current location is month
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->month = atoi(temp);
		
		//current location is year		
		temp[0] = *currentLocation++;
		temp[1] = *currentLocation++;
		
		packet->year = atoi(temp);		
		
		
	}	
	
	return 0;
	
}