GPS-Koordinaten von SD in Array auslesen

Hallo zusammen,
trotz mehrerer Stunden suchen und ausprobieren kann ich für mein Problem keine Lösung (hier und google) finden. Vielleicht kann mir ja jemand weiterhelfen:

In einer txt-Datei auf einer SD-Karte sind GPS-Koordinaten (mind. 3) abgelegt. Diese sollen in ein Array eingelesen werden und anschließend mittels „Point in Polygon“ weiterverarbeitet werden. Das Polygon soll hierbei aus den Koordinaten der txt-Datei bestehen.

Idee für Inhalt der txt-Datei (mit Texteditor erstellt) [longitude,latitude]:
52.518188,13.405258
50.935480,6.959870
48.137526,11.580147

Code für Point in Polygon von hier:
http://forum.arduino.cc/index.php?topic=136265.0

Klar es gibt etliche Beispiele im Netz um Daten von SD auszulesen. Aber die Kombination float und sinnvoll in ein array schreiben finde ich nirgends, Anpassungsversuche meinerseits scheiterten bisher leider auch immer.

Lese den Text in ein char Array (C String ein) und parse ihn dann mit strtok() + atof()

Das hier geht auch mit SD wenn man statt Serial ein File Objekt übergibt:
http://forum.arduino.cc/index.php?topic=361111.msg2494595#msg2494595

Damit meine ich die readSerial() Funktion.

SERIAL_BUFFER_SIZE (besser vielleicht STRING_BUFFER_SIZE) nennen musst du so groß machen dass der String reinpasst. Vielleicht 30.

Dann kann man das nacheinander mehrmals aufrufen um die Zeilen auszulesen.

Eine Zeile zu Parsen kann man so machen:

float f1 = atof(strtok(stringBuffer, ","));
float f2 = atof(strtok(NULL, ","));

Wobei stringBuffer das Array ist. In dem Sketch heißt es serialBuffer, weil das für Serial gedacht war

Oder hier für SD, aber nur mit einer einzigen Zahl:
http://forum.arduino.cc/index.php?topic=371048.msg2558714#msg2558714
Da habe ich es readStream() genannt

Wichtig bei dem Code:
Die letzte Zeile muss auch mit einem Zeilenumbruch abgeschlossen werden! Sonst wird das Ende hier nicht erkannt.

tm967:
In einer txt-Datei auf einer SD-Karte sind GPS-Koordinaten (mind. 3) abgelegt.

Mindestens 3?

Vielleicht aber auch 300?
Oder 3000?

Ich kann Dir nur sagen: RAM-Speicher steht in Mikrocontrollern nur sehr begrenzt zur Verfügung. Bei sehr vielen Punkten in der Datei dürftest Du einen Algorithmus benötigen, der die notwendigen Berechnungen ausführen kann, ohne dass zuerst die gesamte Datei in ein RAM-Array eingelesen werden muss.

Bei 3, 4, oder auch 10 Punkten dürfte ein Array im RAM kein Problem darstellen.
Aber "mindestens 3" kann ja im Endeffekt auch dreihundert oder dreitausend oder noch viel mehr Punkte bedeuten. Und bei einem 3000-Punkte-Polygon dürftest Du vom Algorithmus her anders an die Sache herangehen müssen als "erstmal alles aus der Datei im RAM einlesen".

@Serenifly
Super - vielen Dank für das Aufzeigen des Weges.

Ich habe es nun versucht einzubauen - leider funktioniert es noch nicht ganz. Ein Punkt der in dem Polygon liegt, wird nicht als solcher erkannt. Die Koordinaten der ersten Zeile werden jedoch angezeigt (nur bis zur 2. Nachkommastelle - aber das liegt ja nur an der Anzeige?)

Serenifly:
Dann kann man das nacheinander mehrmals aufrufen um die Zeilen auszulesen.

Blöde Frage, aber wie mach ich das? Sry, aber bin nicht so der große Programmierer ...

#include <SD.h>

const int STRING_BUFFER_SIZE = 30;
char stringBuffer[STRING_BUFFER_SIZE];
const uint8_t polySides = 3;

float polyX[polySides] = {};
float polyY[polySides] = {};

File dataFile;

void setup()
{
  SD.begin();
  Serial.begin(9600);
}

void loop()
{
  dataFile = SD.open("data.txt");
  readStream(dataFile);
    float polyX = atof(strtok(stringBuffer, ","));
    float polyY = atof(strtok(NULL, ","));
      
    Serial.println("polyX:");
    Serial.println(polyX);
    Serial.println("polyY:");
    Serial.println(polyY);

    Serial.println("in Polygon?");
    Serial.println(pointInPolygon(51.067083, 10.198048)); // Sollte drin sein
   delay(10000);
}


bool readStream(Stream& stream)
{
  static byte index;

  while (stream.available())
  {
    char c = stream.read();

    if (c >= 32 && index < STRING_BUFFER_SIZE - 1)
    {
      stringBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      stringBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

bool pointInPolygon( float x, float y )
{
 int i, j = polySides - 1;
 bool oddNodes = false;

 for ( i = 0; i < polySides; i++ )
 {
   if ( (polyY[i] < y && polyY[j] >= y || polyY[j] < y && polyY[i] >= y) &&  (polyX[i] <= x || polyX[j] <= x) )
   {
     oddNodes ^= ( polyX[i] + (y - polyY[i]) / (polyY[j] - polyY[i]) * (polyX[j] - polyX[i]) < x );
   }

   j = i;
 }
 return oddNodes;
}

@jurs
Mit mindestens 3 meinte ich eher, dass das Ganze mit 1-2 Koordinaten ja nur bedingt Sinn ergibt :wink:
Mehr als 6 sollten es nicht werden. Dafür wären 2 Polygone, auf welche getestet wird, schön.

nur bis zur 2. Nachkommastelle - aber das liegt ja nur an der Anzeige?)

Das liegt an Serial.print(). Normal sind da nur 2 Stellen. Als zweiten Parameter kann man die Kommastellen der Anzeige übergeben

Ich habe es nun versucht einzubauen - leider funktioniert es noch nicht ganz. Ein Punkt der in dem Polygon liegt, wird nicht als solcher erkannt.

Das hat dann, aber nichts mit der SD Sache zu tun, oder? Sondern es liegt in deiner pointInPolygon() Funktion.

Ist ^= wirklich was du willst? Das ist ein bitweises XOR. Andererseits ist es fertiger Code. Dann wird es schon passen.

Bist du sicher dass die Koordinaten korrekt sind?

Du vergisst übrigens die Datei wieder zu schließen!

Hier mal ein Test-Programm für eine beliebige Anzahl von Koordinaten:

#include <SD.h>
#include <SPI.h>

const int STRING_BUFFER_SIZE = 30;
char stringBuffer[STRING_BUFFER_SIZE];

struct Coords
{
  float lat;
  float lon;
};

const int POLY_SIDES = 4;
Coords polyCoords[POLY_SIDES];


void setup()
{
  Serial.begin(9600);
  SD.begin();

  File dataFile = SD.open("data.txt", FILE_READ);
  if (dataFile)
  {
    int numOfCoords = 0;

    for (; numOfCoords < POLY_SIDES; numOfCoords++)
    {
      if (readStream(dataFile) == true)
      {
        polyCoords[numOfCoords].lat = atof(strtok(stringBuffer, ","));
        polyCoords[numOfCoords].lon = atof(strtok(NULL, ","));
      }
      else
      {
        Serial.println(F("Keine Daten mehr in Datei"));
        break;
      }
    }

    Serial.print(F("Zeilen: ")); Serial.println(numOfCoords);

    for (int i = 0; i < numOfCoords; i++)
    {
      Serial.print(polyCoords[i].lat, 6);
      Serial.print(',');
      Serial.println(polyCoords[i].lon, 6);
    }

    dataFile.close();
  }
}

void loop()
{
}

bool readStream(Stream& stream)
{
  static byte index;

  while (stream.available())
  {
    char c = stream.read();

    if (c >= 32 && index < STRING_BUFFER_SIZE - 1)
    {
      stringBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      stringBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

Das versucht so viele Zeilen einzulesen wie structs im Array vorhanden sind. Aber wenn vorher das Ende der Datei kommt wird abgebrochen

Lat/Lon sind glaube ich vertauscht :slight_smile:

Super! Vielen, vielen Dank! Jetzt funktioniert alles. :smiley:

Lat/Lon passt so. 8)

Hat jemand eine Idee, wie man am einfachsten die Konstante POLY_SIDES (definiert mit const int) mit der Anzahl der Zeilen (also aus der if-Abfrage = numOfCoords) überschreibt, bzw. den Wert übergibt.

Ziel ist, auf die SD-Karte 3, 4, 5, ... 10 Zeilen mit Lat/Lon-Koordinaten zu schreiben und dann eine richtige Berechnung mittels "Point in Polygon" durchzuführen, ohne im Programmcode den POLY_SIDES Wert manuell verändern zu müssen.

Meine Idee wäre:

  1. in IF-Abfrage prüfen, ob POLY_SIDES = numOfCoords, wenn nicht, Wert in EEPROM schreiben und neustart auslösen
  2. bei jedem Start const int POLY_SIDES = Wert aus EEPROM

Scheint aber recht kompliziert ... gibt es einen einfacheren / schöneren Weg?

Du kannst die Zahl auch in die erste Zeile der Datei schreiben.

Man kann auch einmal über die Datei gehen und erst mal nur die Anzahl der Zeilen zählen. Und dann ein zweites mal um die Zeilen auszulesen.

Das ist dann keine Konstante mehr, sondern eine Variable :slight_smile:

Mit dem Array bekommst du dann aber ein kleines Problem. Die Größe von globalen Arrays muss zur Compile-Zeit bekannt sein. Lokale Arrays können in avr-gcc (nicht in Standard C!) auch eine variable Länge haben.
Hier musst du dir überlegen ob du die Koordinaten wirklich dauerhaft speichern willst. Wenn du nur die Funktion füttern willst, müssen die eigentlich nicht global gespeichert werden und es reicht das Array lokal in der Funktion zu deklarieren. Wenn doch, wäre auch dynamischer Speicher noch eine Option, auch wenn es hier nicht gerne gesehen wird.

tm967:
Ziel ist, auf die SD-Karte 3, 4, 5, ... 10 Zeilen mit Lat/Lon-Koordinaten zu schreiben und dann eine richtige Berechnung mittels "Point in Polygon" durchzuführen, ohne im Programmcode den POLY_SIDES Wert manuell verändern zu müssen.

Meine Idee wäre:

  1. in IF-Abfrage prüfen, ob POLY_SIDES = numOfCoords, wenn nicht, Wert in EEPROM schreiben und neustart auslösen
  2. bei jedem Start const int POLY_SIDES = Wert aus EEPROM

Scheint aber recht kompliziert ... gibt es einen einfacheren / schöneren Weg?

Meine Idee wäre, den Wert als Maximalwert für die Anzal der Datenpaare zu definieren, also z.B. 10:

const int POLY_SIDES = 10; // so viele Werte dürfen maximal in der Datei stehen

Und in der Einleseroutine zählst Du mit, wie viele Datenpaare tatsächlich eingelesen werden:

int numPoints= 0; // die Anzahl der tatsächlich vorhandenen Werte
...
...
...
 if (readStream(dataFile) == true)
      {
        polyCoords[numOfCoords].lat = atof(strtok(stringBuffer, ","));
        polyCoords[numOfCoords].lon = atof(strtok(NULL, ","));
        if (numPoint<POLY_SIDES)  numPoints++; // ein weiteres Wertepaar wurde gelesen (bis zur Maximalanzahl)
      }

Dann steht die tatsächliche Punktezahl nach dem Einlesen in der Variablen 'numPoints'.

Und innerhalb Deiner pointInPolygon() Funktion ersetzt Du alle Zugriffe auf die deklarierte Maximalanzahl in den Arrays ('polySides') durch Zugriffe auf die tatsächlich verwendete Anzahl ('numPoints').

So funktioniert der Test-Code auch schon :slight_smile:

Es werden maximal POLY_SIDES Paare eingelesen und man bekommt die tatsächliche Anzahl in numOfCoords. Die for-Schleife bricht mit break ab wenn das Ende der Datei kommt. Kann man natürlich auch anders zählen.

Ah ja, so einfach kann es sein ... Besten Dank! 8)

Ohne diesen "Zusatz" klappt der Point in Polygon-Code nicht. Verwendet man nur 3 Koordinatenpaare und lässt const int POLY_SIDES = 4, so bekommt man auch außerhalb des Polygons an manchen Stellen falsch-richtige Ergebnisse.

Ändere pointInPolygon() einfach so dass du die Anzahl als dritten Parameter übergibst statt auf die globale Konstante zuzugreifen