August 2023
A geofence is a virtual boundary or perimeter defined using GPS (Global Positioning System) coordinates. Typically a GPS equipped device determines when it approaches or crosses the geofence boundary.
The GPS signals provide accuracy to 0.643 m or 2.1 ft 95% of the time. (Selected Availability ended May 2000). Nearly all GPS devices have an accuracy limited to 2 meters in open sky conditions using Wide Area Augmentation System (WAAS) for the USA.
For this project, a geofence border is defined by two GPS coordinates and a line between them. The coordinates for two end points of the line were determined using Google maps. The code running on the microcontroller calculates the distance between the GPS and the geofence border (line) and then considers the border violated when the distance is less than 5 meters.
The image below shows the geofence border at the bottom, and the simulated GPS position at the top of the property. The geofence border points are obtained graphically using Google Maps running on a PC (not mobile) In the map, click on the point you want to get the GPS position for and it will be displayed in a popup window.
Search online for C++ code that calculates distance between two GPS positions and for the distance between a GPS position and a line, and you will find a lot of WRONG information. For very long distances between the endpoints of the line, and the point to the line, it is necessary to account for the curvature of the earth in the distance calculation. But for this project, it is assumed the point to be monitored relative to the geofence border is not so far, so a cartesian coordinate system solution is employed.
double haversineDistance(double lat1, double lon1, double lat2, double lon2) {
// Returns the distance in meters between two GPS coordinates using the
// Haversine formula.
// The Haversine formula doesn't account for the Earth being a spheroid
// usage:
// double d = haversineDistance(40.441280, -76.122904, 40.44041, -76.12287);
// Serial.println(d,1); // 96.7 m correct.
const double R = 6371000.0; // Earth's radius in m
const double dLat = (lat2 - lat1) * M_PI / 180.0;
const double dLon = (lon2 - lon1) * M_PI / 180.0;
const double a = sin(dLat / 2) * sin(dLat / 2) +
cos(lat1 * M_PI / 180.0) * cos(lat2 * M_PI / 180.0) *
sin(dLon / 2) * sin(dLon / 2);
const double c = 2 * atan2(sqrt(a), sqrt(1 - a));
const double distance_m = R * c;
return distance_m;
} // haversineDistance()
double pointToLineDistance(double pointLat, double pointLong,
double lineLatA, double lineLongA,
double lineLatB, double lineLongB) {
// Returns the distance in meters between a GPS location
// (pointLat, pointLong) and a line (geofence border) defined by
// the GPS coordinates of lineLat1,lineLongA and lineLatB, lineLongB.
// All GPS coordinates in decimal degrees where south and
// west are negative.
// If the point is outside the end points, then the closest distance
// to the endpoint(s) is calculated and returned.
double distance_to_line_start = haversineDistance(pointLat, pointLong, lineLatA, lineLongA);
double distance_to_line_end = haversineDistance(pointLat, pointLong, lineLatB, lineLongB);
// Convert to cartesian coordinate sytem
double x = pointLong;
double y = pointLat;
double x1 = lineLongA;
double y1 = lineLatA;
double x2 = lineLongB;
double y2 = lineLatB;
// Source: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
double distance_to_line_cartesion = (abs((x2-x1)*(y1-y) - (x1-x)*(y2-y1))) / (sqrt(pow((x2-x1),2) + pow((y2-y1),2)));
distance_to_line_cartesion = distance_to_line_cartesion * 111320.0; // 1 decimal degree = 111,320 m
return min(distance_to_line_cartesion, min(distance_to_line_start,distance_to_line_end));
} // pointToLineDistance()
// The geofence border (line) is defined by the following points:
const double lat1 = 40.44041;
const double lon1 = -76.12287;
const double lat2 = 40.44042;
const double lon2 = -76.12200;
void setup() {
// 40.443217, -76.124517
double lat = 40.443217;
double lon = -76.124517;
// pointToLineDistance(double pointLat, double pointLong,double lineLatA, double lineLongA,double lineLatB, double lineLongB)
double dist_m = pointToLineDistance(lat, lon, lat1, lon1, lat2, lon2);
// Result: 315 m
}
void loop() {
}
This project required fast update of the GPS position. After some trial and error, I found that I could increase the GPS update from 1 Hz to 10 Hz after a GPS fix was found and the fix quality was good. I also had to reduced the GPS message content to just the latitude/longitude position in order to support the 10 Hz update. This allowed me to check the GPS position every 200 ms or at a rate of 5 Hz.
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.