Check if GPS is within a rectangular area

Hello,

I am creating GPS based audio guide and searching for help. Is it possible to simply determine someone’s location in rectangle? And relate that rectangle with an action - to play selected audio track?

I have SparkFun Venus GPS with 2.5m accuracy, Arduino Nano and SparkFun WAV-Trigger, which can play and mix audio tracks.

Also, attaching picture with explanation.

WAV-Trigger uses WAV-Trigger and AltSoftSerial libraries. Is it possible to use the same software serial for both devices? WAV-Trigger needs TX signal, Venus GPS needs RX signal.

Thank you very much.

I am newbie to the Arduino and coding but learning fast.

Yes, you can determine if a pair of coordinates is within a specific rectangular area. It is almost trivial if the rectangle is aligned with the coordinate system: if latitude is between the two latitude limits and longitude is between the two longitude limits you are in the rectangle. If the rectangle is skewed it becomes a little harder but it is a solved problem. Google: coordinates within a rectangle.

Are the GPS and WAV-Trigger using the same baud rate? If so you should be able to receive from the GPS and send to the WAV-Trigger.

Is it possible to simply determine someone’s location in rectangle?

Are you trying to determine if the GPS is inside a rectangle? Or, are you trying to determine where it is within that triangle (i.e. 27 meters east of first corner and 45 meters south)?

Either is possible.

Image embedded for our convenience:

f3f3959bcaae4f0686bc7865fad63e0d197c8b52.jpg
Technique described here.

Will the guide be used indoors or outdoors?

If the audio should play when you are “near” a point of interest, it would be easy to calculate the listener’s distance from that point. Then play the audio when the distance drops below a threshold. This would be a circular area, not a rectangular area.

Cheers,
/dev

portoparasas: I have SparkFun Venus GPS with 2.5m accuracy,

Bear in mind that the 2.5M is the accuracy the GPS is capable of, you may not get that accuracy of course.

" Is it possible to simply determine someone's location in rectangle? " Is the rectangle outdoors? Indoors with windows? Indoors with no windows?

I have a DFRobot GSM/GPS module. In my living room with 2 windows that are partially obscured by fir type trees, it takes several minutes to get satellite lock and report a position (quite few, as in, I wonder if it will return a value sometimes, especially if the trees are wet). Entering data received from the GPS into maps.google.com in the format xx.xxx, xx.xxx it shows my position as the middle my neighbor's yard. Add one more digit and I can find the middle of my house. Add two more decimal places (5 total) of location and I can pick the corners of my house. I can see in the 2nd number (east west) that google maps shows xx.52840 as the east side of my house and .52860 as the west side, and xx.36715 as the southern face of my house and xx.36725 as the back.

I think this GPS returns data as xxxx.xxxxx, xxxx.xxxxx, and Google will accept all 7 digits as well, but it's looking for 41.xxxxx, -70.xxxxx format for the Boston area for example.

But you need to be able to see satellites.

srnet:
Bear in mind that the 2.5M is the accuracy the GPS is capable of, you may not get that accuracy of course.

sp. “2.5 m”

Thank you for the replies. Here some information:

  1. This project will be held in exterior only.

  2. Selected audio file should play inside green rectangle area (picture).

  3. All rectangles are separated by minimum 50 meters, so there shouldn’t be any mistakes in determining location.

GPS guide.jpg

I found information written by Michael Krumpus. He created “Speed Trap! A GPS-Based Speeding Alert” . This code was created to check if car is speeding in different areas. Could you please tell me how to modify this code for determining someones location in rectangular area?

Thank you very much.

#include <NewSoftSerial.h>
#include <TinyGPS.h>

#define NUM_OFF 2
#define DELAY 50
#define TEST_BUTTON 9
#define DEFAULT_SPEED_LIMIT 55
#define NSPEEDZONES 6

boolean debugMode = true;

boolean speeding = false;
int blue[4];
int red[4];
int headlight1;
int headlight2;

class Vertex {
public:
  Vertex(float llat, float llng) {
    lat = llat;
    lng = llng;
  }
  float lat;
  float lng;
};

class SpeedZone {
public:
  SpeedZone(int s) {
    speedLimit = s;
  }
  void setVertices(int n, Vertex *v) {
    nVertices = n;
    vertices = v;
  }
  int nVertices;
  Vertex *vertices;
  int speedLimit;
};


SpeedZone *speedZones[NSPEEDZONES];
TinyGPS gps;
NewSoftSerial nss(2, 3);
boolean buttonPressed = false;

void setup() {
  Serial.begin(115200);

  setupSpeedZones();

  blue[0] = 12;
  blue[1] = 13;
  blue[2] = 14;
  blue[3] = 15;
  red[0] = 16;
  red[1] = 17;
  red[2] = 18;
  red[3] = 19;
  headlight1 = 10;
  headlight2 = 11;

  for(int i=0;i<4;i++) {
    pinMode(blue[i], OUTPUT);
    pinMode(red[i], OUTPUT);
  }
  pinMode(headlight1, OUTPUT);
  pinMode(headlight2, OUTPUT);
  pinMode(TEST_BUTTON, INPUT);
  digitalWrite(TEST_BUTTON, HIGH);

  randomSeed(analogRead(0));

  // Allow EM-406a to power up
  delay(3000);

  // Establish serial connection to EM-406a
  nss.begin(4800);
}



void setupSpeedZones() {
  // Rockford Road
  speedZones[0] = &SpeedZone(45);
  speedZones[0]->setVertices(6, (Vertex[6]){
      Vertex(45.027162772967756, -93.48137855529785),
      Vertex(45.02946790848425, -93.4742546081543),
      Vertex(45.02955889877115, -93.46193790435791),
      Vertex(45.02861865883124, -93.46172332763672),
      Vertex(45.02861865883124, -93.47412586212158),
      Vertex(45.02649547957147, -93.48133563995361)});


  // Schmidt Lake Rd
  speedZones[1] = &SpeedZone(45);
  speedZones[1]->setVertices(10, (Vertex[10]){
      Vertex(45.044176126280206, -93.48219394683838),
      Vertex(45.04390322470628, -93.47322463989258),
      Vertex(45.043387740403595, -93.46974849700928),
      Vertex(45.0440548368525, -93.46498489379883),
      Vertex(45.0440548368525, -93.46185207366943),
      Vertex(45.04332709488612, -93.46185207366943),
      Vertex(45.0433574176529, -93.46580028533936),
      Vertex(45.04272063617576, -93.46983432769775),
      Vertex(45.043266449304376, -93.47434043884277),
      Vertex(45.04332709488612, -93.48215103149414)});


  // Vicksburg Lane
  speedZones[2] = &SpeedZone(50);
  speedZones[2]->setVertices(4, (Vertex[4]){
      Vertex(45.042993543391, -93.48219394683838),
      Vertex(45.04320580365836, -93.4812068939209),
      Vertex(45.02722343561804, -93.48116397857666),
      Vertex(45.02725376691909, -93.4823226928711)});
		

  // I-494
  speedZones[3] = &SpeedZone(65);
  speedZones[3]->setVertices(8, (Vertex[8]){
      Vertex(45.04453999302037, -93.45356941223145),
      Vertex(45.03950629768142, -93.45369815826416),
      Vertex(45.029255597252295, -93.45378398895264),
      Vertex(45.02594950646131, -93.45386981964111),
      Vertex(45.02616182995624, -93.4522819519043),
      Vertex(45.028921963725224, -93.45168113708496),
      Vertex(45.0390514238751, -93.4520673751831),
      Vertex(45.044267093182235, -93.45150947570801)});
		

  // Fernbrook Lane
  speedZones[4] = &SpeedZone(40);
  speedZones[4]->setVertices(4, (Vertex[4]){
      Vertex(45.04317548081123, -93.4621524810791),
      Vertex(45.04332709488612, -93.46099376678467),
      Vertex(45.029680218928654, -93.46065044403076),
      Vertex(45.02961955888205, -93.46189498901367)});

		
  // Bounding rectangle for residential areas
  // This should be defined last in the list because it's the "catch all" speed zone.
  speedZones[5] = &SpeedZone(25);
  speedZones[5]->setVertices(4, (Vertex[4]){
      Vertex(45.045783186920296, -93.48395347595215),
      Vertex(45.0456315793546, -93.44983577728271),
      Vertex(45.02585851043667, -93.45009326934814),
      Vertex(45.02594950646131, -93.48326683044434)});


  if (debugMode) {
    printSpeedZones();
  }

}

/*
 * This is the point-in-polygon algorithm adapted from
 * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
 */
boolean inSpeedZone(int speedZone, float lat, float lng) {
  SpeedZone *s = speedZones[speedZone];

  int i, j;
  boolean inside = false;
  for (i = 0, j = s->nVertices-1; i < s->nVertices; j = i++) {
    if ( ((s->vertices[i].lat > lat) != (s->vertices[j].lat > lat)) &&
         (lng < (s->vertices[j].lng - s->vertices[i].lng) * (lat - s->vertices[i].lat) / (s->vertices[j].lat - s->vertices[i].lat) + s->vertices[i].lng) )
       inside = !inside;
  }

  return inside;
}


boolean inSpeedZone(int speedZone) {
  float lat, lng;
  unsigned long  fix_age;

  // retrieves +/- lat/long in 100,000ths of a degree
  gps.f_get_position(&lat, &lng, &fix_age);

  return inSpeedZone(speedZone, lat, lng);
}

void loop() {

  if (readGPS()) {
    if (debugMode) {
      debug();
    }
    speeding = isSpeeding();
  }

  if (digitalRead(TEST_BUTTON) == LOW) {
    buttonPressed = true;
  } else {
    buttonPressed = false;
  }
  if ((speeding || buttonPressed) && (!(speeding && buttonPressed))) {
    policeLights();
  } else {
    allOff();
  }

}

bool readGPS() {
  while (nss.available()) {
    if (gps.encode(nss.read())) {
      return true;
    }
  }
  return false;
}

  
int getSpeedLimit() {
  boolean isInSpeedZone;

  for(int i=0;i<NSPEEDZONES;i++) {
    isInSpeedZone = inSpeedZone(i);
    if (isInSpeedZone) {
      return speedZones[i]->speedLimit;
    }
  }
  return DEFAULT_SPEED_LIMIT;
}

boolean isSpeeding() {
  int speed = (int)(gps.f_speed_mph() + 0.5);
  int speedLimit = getSpeedLimit();

  if (speed > speedLimit) {
    return true;
  }
  return false;
}

boolean policeLights() {

  // The white LEDs are so bright we actually turn them down quite a bit.
  // 0 is full brightness and 255 is off (because the cathode is connected to the PWM pin)
  analogWrite(headlight2, 200);
  analogWrite(headlight1, 200); 

  allOn();

  // Turn off some of the red and blue lights to give them a flashing effect.
  for(int i=0;i<NUM_OFF;i++) {
    digitalWrite(blue[random(4)], HIGH);
    digitalWrite(red[random(4)], HIGH);
  }
  delay(DELAY);

}

void allOn() {
  for(int i=0;i<4;i++) {
    digitalWrite(blue[i], LOW);
    digitalWrite(red[i], LOW);
  }
}

void allOff() {
  for(int i=0;i<4;i++) {
    digitalWrite(blue[i], HIGH);
    digitalWrite(red[i], HIGH);
  }
  digitalWrite(headlight1, HIGH);
  digitalWrite(headlight2, HIGH);
}

void printSpeedZones() {

  for(int i=0;i<NSPEEDZONES;i++) {
    SpeedZone *s = speedZones[i];
    Serial.println(s->speedLimit);
    for(int v=0;v<s->nVertices;v++) {
      Serial.print("(");
      Serial.print(s->vertices[v].lat);
      Serial.print(", ");
      Serial.print(s->vertices[v].lng);
      Serial.println(")");
    }
  }
}

void debug() {
  long lat, lon;
  unsigned long fix_age, time, date, speed, course;

  // retrieves +/- lat/long in 100000ths of a degree
  gps.get_position(&lat, &lon, &fix_age);

  Serial.println(getSpeedLimit());

  Serial.print("lat: ");
  Serial.print(lat);
  Serial.print("    lng: ");
  Serial.print(lon);
  Serial.print("    speed: ");
  Serial.println(gps.f_speed_mph());
}

Rectangular areas are easy, no need for complicated algorithms. In general you subtract one vertex (origin) from the given coordinates, and compare the result with the difference to the opposite vertex (extent). A practical implementation of the coordinates of a rectangle is (untested!):

struct {
long lon, lat; //origin
unsigned long width, height; //extent
}
//get coords
long lon, lat;
unsigned long fix_age;
gps.getposition(&lon, &lat, &fix_age);
//check in/outside
unsigned long w, h;
w = lon - rect.lon; 
h = lat - rect.lat;
inside = (w <= rect.width) && (h <= rect.height);

Two tricks are used:
First the coordinates must be sorted, so that width and height become positive integers.
Second the unsigned difference is compared, where a negative signed difference becomes a very high unsigned value, higher than the width or height. This trick also is used in the typical time interval computation (millis()-start<=interval) only that now start and interval are taken from the rectangle origin and extent.

I hope that I translated lon (WE) and lat (NS) right, and the computation eventually must be transformed into above standard time interval code pattern, for proper unsigned comparison.

As a newbie it's hard to understand but I will try my best.

Thank you very much.