nngh 发表于 2013-3-28 16:14:26

Processing处理GPS数据实验(更新,用谷歌地图定位)

本帖最后由 nngh 于 2013-5-27 12:10 编辑

前几天用Arduino UNO R3和GPS接收板做了一个简易GPS,在这里http://www.geek-workshop.com/thread-3778-1-1.html。然后就开始琢磨用PC机来处理GPS数据,因为

那块GPS接收板是插在Arduino上的不想拔下来(同时还要用LCD显示GPS数据的),而且Arduino串口是有数据发送出来的,那么用PC机接收COM口数据,再用Processing,来处理,不就行了么?于是就开始下面的实验:

   首先,修改Arduino串口输出数据的格式。
   由于GPS原始数据流中时间,定位,速度等信息已经由Arduino处理完成,那么PC机只需要接收现成的等现成数据用于绘图,无需再次解码。
   自定义Arduino输出格式如下:
      
例如,对于数据流
A,061543.000,2248.8364,N,10820.1171,E,0.00,307.67,280313,,*
A,061544.000,2248.8364,N,10820.1171,E,0.00,307.67,280313,,*
.....

每个数据用逗号“,”分隔说明如下,
A--------061544.000-------2248.8364---N------------10820.1171-----E-----------0.00-------------307.67------------280313---
0            1                           2             3                         4            5               6                      7                     8
0-Data OK
1-timestamp (UTC)
2- latitude   
3-North/South
4-Longitude
5- East/West
6-Speed over ground
7-Course over ground
8-DD/MM/YY

Arduino程序:
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#define DADOS_LEN 100
#define IDLEN 6
#define TEMPLEN 11
#define GPRMC 0
#define GPGGA 1
char data; //buffer for GPS data
byte conta=0; //variavel auxiliar para contar
char* idnmea[] = {
"$GPRMC","$GPGGA"}; //IDs dos NMEA que vou utilizar
byte verificador[]= {
0,0}; //variavel auxiliar para verificar ID NMEA
byte indice; //Em uma linha $GPRMC contem 12 valores separados por virgulas. Esta variavel guarda a posi&ccedil;&atilde;o do caracter de inicio de cada valor.
byte contindice=0; //variavel auxiliar de controle usada na variavel indice[];
byte menu=0; // Menu do LCD: 0-key RIGHT, 1-key UP, 2-key DOWN, 3-key LEFT, 4-key SELECT
char tempmsg; //variavel temporaria auxiliar para guarda o valor de um dado extraido do GPS.
SoftwareSerial nss(3, 2);
LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
intadc_key_val ={
30, 150, 360, 535, 760 }; //valores do divisor de tens&atilde;o do teclado do LCD Shield
#define NUM_KEYS 5 //numero de teclas do teclado
int adc_key_in; //valor da entrada analogica do teclado
byte key=-1; //tecla pressionada
byte oldkey=-1; //tecla pressionada anteriormente

void setup()
{
Serial.begin(9600);
nss.begin(9600);
lcd.clear(); //Clear LCD
lcd.begin(16,2);
lcd.print("Arduino.cc");
lcd.setCursor(0, 2); //set cursor on LCD at col 0 and row 2
lcd.print("Arduino GPS");
clearBuffer(); //clear buffer for GPS (databuffer)
clearTemp(); //clear data for GPS (tempmsg)
//Serial.begin(9600); //Inicia UART para comunicar com módulo GPS
delay(3000);
}

void loop(){   
while(nss.available())
{//if serial port available
    data = nss.read(); //Read a byte of the serial port
    if(data==13)
       {//If the received byte is = to 13, end of transmission
            verificador=0; //verifies idnmea ($GPRMC)
         //verificador=0;//verifies idnmea ($GPGGA)
            for(byte i=1;i<=IDLEN;i++)
                { //checking the ID NMEA of string received
                   if(data==idnmea)
                     {      //Verifies that is $GPRMC
                      verificador++; //increases 1
                      }

               }
             if(verificador==IDLEN){ // if the line received is $GPRMC
               //A line string of GPRMC has 11 "," Divided into 12 data
            //exemplo: $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
             //            0      1   2    3    4   5    6    7    8   9   10    11
            // we interesting: 2-timestamp (UTC), 3-latitude, 4-North/South, 5-Longitude, 6-East/West,7-Speed in knots,9-date stamp
                  contindice = 0;
                  indice = 1; //data[] inicia no caracter 1
                  contindice++;
             for(byte i=1; i<DADOS_LEN;i++){ //crosses every line data[] identifying where each value of GPS
          if(data==','){ //found the final of a data
            indice = i+1;
            contindice++;
          }
      }
      adc_key_in = analogRead(0); //verifica entrada analogica do teclado
      key = get_key(adc_key_in); //interpreta valor da entrada analogica
      if (key != oldkey){ //verifica se o valor encontrado é diferente do valor anterior
          delay(50);                // faz um delay para o debounce
          adc_key_in = analogRead(0);   
          key = get_key(adc_key_in);                        // interpreta
          if (key != oldkey){                        //verifica se é diferente
            oldkey = key; //atualiza oldkey
            lcd.clear(); //clear LCD
            if (key >=0){ //se alguma tecla foi pressionada
            menu = key; //atualiza o menu
            }
          }
      }
      Serial.print(datastream(2));
         Serial.print(",");
         Serial.print(datastream(1));
         Serial.print(",");
         Serial.print(datastream(3));
         Serial.print(",");
         Serial.print(datastream(4));
         Serial.print(",");
         Serial.print(datastream(5));
         Serial.print(",");
         Serial.print(datastream(6));
         Serial.print(",");
         Serial.print(datastream(7));
         Serial.print(",");
         Serial.print(datastream(8));
         Serial.print(",");
         Serial.print(datastream(9));
         Serial.print(",");
         Serial.print(datastream(10));
         Serial.print(",");
         Serial.print(datastream(11));
         //Serial.print("*");
         Serial.println(" ");
      switch(menu){
          //show onLCD the key pressed
      case 4: //SELECT 4-key
          lcd.setCursor(1,0); //move cursor to LCDcolumn 1 row 0
                lcd.print("True course"); //print text to LCD
                lcd.setCursor(1,1); //move cursor to LCDcolumn 1 row 1
                lcd.print(datastream(7)); //print text to LCD
          break;            
      case 3: //LEFT 3-key
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print("Lat:");
          lcd.print(datastream(4));
          lcd.print(" ");
          lcd.print(datastream(3)); // datastream(byte)
          lcd.setCursor(0, 1);
          lcd.print("Lon:");
          lcd.print(datastream(6));
          lcd.print(" ");
          lcd.print(datastream(5));
          break;
      case 1: //UP   1-key
          lcd.setCursor(1,0);
          lcd.print("Date:");
          lcd.print(datastream(9));
          lcd.setCursor(1,1);
          lcd.print("Time:");
          lcd.print(datastream(1));
          break;
      case 2: //DOWN2-key
          lcd.setCursor(1,0);
          lcd.print("Speed:");
          lcd.print(datastream(7));
          lcd.setCursor(1,1);
          lcd.print("knots");
          break;
      }

      }

      conta = 0; //zero conta, or is, will start next line of GPS and data this at position 0
      clearBuffer(); //clear data[]
    }
    else{
      conta++; //increases conta, in other words, data skips to the next position
    }

}
}
void clearBuffer(){
for (byte i=0;i<DADOS_LEN;i++){       // clear variavel (buffer) received GPS data
    data=' ';
}
}
void clearTemp(){
for(byte i=0;i<TEMPLEN;i++)
    tempmsg=' ';
}

char* datastream(byte inicio){
/*
Receive Datastream from GPS devices,then convert to Data We can read directly
   remenber that: 2-timestamp (UTC), 3-latitude, 4-North/South, 5-Longitude, 6-East/West,7-Speed in knots,9-date stamp
   */
clearTemp();
byte i;
byte fim = indice-2;
inicio = indice;
for(i=0;i<=(fim-inicio);i++){
    tempmsg = data;
}
tempmsg = '\0';
return tempmsg;
}

// Convert ADC value to key number
byte get_key(unsigned int input)
{
int k=menu;
for (byte i = 0; i < NUM_KEYS; i++){
    if (input < adc_key_val){
      k=i;
      return k;
    }
}
return k;

}



   其次:用Processing的串行数据库processing.serial.*来接收来自Aduino的数据,用serialEvent()函数将数据放入一维数组中,再分别提取后进行格式转换,最后在PC机屏幕上显示出来,效果如图。
Processing程序参考了Making Things Talk Second Edition Tom Igoe (O"REILLY出版),第8章 How to Locate(Almost) Anything --Reading the GPS Serial--Project 19 Protocol中大部分,本人菜鸟一个,基本不会编程。
请注意,Processing程序中 String portName = Serial.list(); 一行,需将方括号[]中的数字换成你的Arduino接PC机的串口实际值,并注意修改你用的GPS波特率(我的是9600bps)。
Processing程序:
/*
GPS parser
Context: Processing
This program takes in NMEA 0183 serial data and parses
out the date, time, latitude, and longitude using the GPRMC sentence.
*/
// import the serial library:
import processing.serial.*;

Serial myPort;         // The serial port
float latitude = 0.0;    // the latitude reading in degrees
String northSouth = "N"; // north or south?
float longitude = 0.0;   // the longitude reading in degrees
String eastWest = "W";   // east or west?
float heading = 0.0;   // the heading in degrees

int hrs, mins, secs;      // time units
int currentDay, currentMonth, currentYear;

float textX = 50;          // position of the text on the screen
float textY = 30;

void setup() {
size(400, 400);      // window size

// settings for drawing:
noStroke();
smooth();

// List all the available serial ports
println(Serial.list());

// Open whatever port is the one you're using.
// for a Bluetooth device, this may be further down your
// serial port list:
String portName = Serial.list();
myPort = new Serial(this, portName, 9600);

// read bytes into a buffer until you get a carriage
// return (ASCII 13):
myPort.bufferUntil('\r');
}

void draw() {
// deep blue background:
background(#0D1133);
// pale slightly blue text:
fill(#A3B5CF);
// put all the text together in one big string:
// display the date and time from the GPS sentence
// as MM/DD/YYYY, HH:MM:SS GMT
// all numbers are formatted using nf() to make them 2- or 4-digit:
String displayString = nf(currentMonth, 2)+ "/"+ nf(currentDay, 2)
    + "/"+ nf(currentYear, 4) + ", " + nf(hrs, 2)+ ":"
    + nf(mins, 2)+ ":"+ nf(secs, 2) + " GMT\n";

// display the position from the GPS sentence:
displayString = displayString + latitude + " " + northSouth + ", "
    + longitude + " " + eastWest + "\n";

// display the heading:
displayString = displayString + "heading " + heading + " degrees\n";

text(displayString, textX, textY);

// draw an arrow using the heading:
drawArrow(heading);
}


void serialEvent(Serial myPort) {
// read the serial buffer:
   String myString = myPort.readStringUntil('\n');
// if you got any bytes other than the linefeed, parse it:
if (myString != null) {
    print(myString);
    parseString(myString);
}
}

void parseString (String serialString) {
// split the string at the commas:
String items[] = (split(serialString, ','));

// if the first item in the sentence is the identifier, parse the rest
if (items.equals("A")) {
    // $GPRMC gives time, date, position, course, and speed
    getRMC(items);
}
}


void getRMC(String[] data) {
// move the items from the string into the variables:
int time = int(data);
// first two digits of the time are hours:
hrs = time/10000;
// second two digits of the time are minutes:
mins = (time % 10000)/100;
// last two digits of the time are seconds:
secs = (time%100);

// if you have a valid reading, parse the rest of it:
// if (data.equals("A")) {
    latitude = minutesToDegrees(float(data));

    northSouth = data;
    longitude = minutesToDegrees(float(data));
    eastWest = data;
    heading = float(data);
    int date = int(data);
    // last two digits of the date are year.Add the century too:
    currentYear = date % 100 + 2000;
    // second two digits of the date are month:
    currentMonth =(date % 10000)/100;
    // first two digits of the date are day:
    currentDay = date/10000;
// }
}

float minutesToDegrees(float thisValue) {
// get the integer portion of the degree measurement:
int wholeNumber = (int)(thisValue / 100);
// get the fraction portion, and convert from minutes to a decimal fraction:
float fraction = (thisValue - ( wholeNumber ) * 100) / 60;
// combine the two and return it:
float result = wholeNumber + fraction;
return result;
}


void drawArrow(float angle) {
// move whatever you draw next so that (0,0) is centered on the screen:
translate(width/2, height/2);

// draw a circle in light blue:
fill(#457DC0);
ellipse(0, 0, 100, 100);

// make the arrow the same as the background::
fill(#0D1133);
// rotate using the heading:
rotate(radians(angle));

// draw the arrow.center of the arrow is at (0,0):
triangle(-20, 0, 0, -40, 20, 0);
rect(-4, 0, 8, 40);
}



继续实验,是否能在地图上定位我们的位置呢?在网上搜索了一下,有人做了一个调用谷歌地图的库函数,这里http://googlemapper.pt.vu/谷歌地图库函数,文件在这里 http://allschedulesapp.com/coisasdoluis/downloads/googleMapper.jar。
首先要取得定位用的地图做底图,然后将Arduino GPS发过来的数据流解码,在底图上标示出来,完成定位。
上面网站提供范例代码,有processing的;下载下来,将其中的double maCenterLat = xx.xxxxxx;
      double mapCenterLon = xxx.xxxxxx;两句d的xxx.xxxxxx替换为你需要的地图的中心点经纬度。
如何取得你的地图中心的经纬度坐标呢?登录http://itouchmap.com/latlong.html,放大地图,找到你需要的地点,点击鼠标,左下方空格就会出现点击处的经纬度了。如图:定点.jpg
运行如下代码:

import googlemapper.*; //导入库函数
PImage map;
GoogleMapper gMapper;

public void setup() {

      size(1280,1024); // 地图尺寸1280x1024
      
      double maCenterLat = xx.xxxxxx;   //地图中心点纬度
      double mapCenterLon = xxx.xxxxxx;//地图中心点经度
      int zoomLevel = 19; //放大级数,不可超过20
      String mapType = GoogleMapper.MAPTYPE_HYBRID; //地图类型
      int mapWidth=1280;
      int mapHeight=1024;
      
      gMapper = new GoogleMapper(maCenterLat, mapCenterLon, zoomLevel, mapType, mapWidth,mapHeight);
      
      map = gMapper.getMap();
}

public void draw() {
      
      image(map,0,0);
      
      saveFrame("map.jpg"); // 生成文件
      
      println(gMapper.x2lon(0));println(gMapper.x2lon(1280)); //输出左右顶点位置
      println(gMapper.y2lat(0));println(gMapper.y2lat(1024)); //输出上下下定点位置
      
      noLoop();
}

运行程序,得到map.jpg,即为你想得到的底图,可以用来做定位的地图了。同时记录下processing下部状态窗口显示的上、下顶点位置,后面程序要用的。
下面是processing用GPS定点后显示位置的主程序:
import processing.serial.*;            // import the serial library:
PImage backgroundMap;
Serial myPort;
float mapGeoLeft   = xxx.xxxxxx;          // Longitude west,前面计算出来的左定点数据,看截图
float mapGeoRight= xxx.xxxxxx;          // Longitude east,前面计算出来的右定点数据,看截图
float mapGeoTop    = xx.xxxxxx;          // Latitudenorth,前面计算出来的上定点数据,看截图
float mapGeoBottom = xx.xxxxxx;          // Latitudesouth.前面计算出来的下定点数据,看截图
float latitude=0.0;    // the latitude reading in degrees
float longitude=0.0 ;   // the longitude reading in degrees
float mapScreenWidth,mapScreenHeight;// Dimension of map in pixels.
float x ;
float y ;

void setup()
{
size(1280,1024);
smooth();
// noLoop();
backgroundMap   = loadImage("xx.jpg");//xx.jpg换成前面截图程序生成底图的文件名
mapScreenWidth= width;
mapScreenHeight = height;
println(Serial.list());
String portName = Serial.list();// serial port list:
myPort = new Serial(this, portName, 9600);// read bytes into a buffer until you get a carriage
myPort.bufferUntil('\r');// return (ASCII 13):
}

void draw()
{
image(backgroundMap,0,0,mapScreenWidth,mapScreenHeight);
//将接收到的经纬度数据转换成地图上的,x,y坐标
x = ( longitude - mapGeoLeft ) / ( mapGeoRight - mapGeoLeft ) * mapScreenWidth;
// y = ( mapGeoTop - latitude) / ( mapGeoTop - mapGeoBottom ) * mapScreenHeight;
y = mapScreenHeight - mapScreenHeight*(latitude-mapGeoBottom)/( mapGeoTop-mapGeoBottom );
fill(0,100,210,0);
strokeWeight(10);
ellipse(int(x),int(y),5,10);//gps定点显示
println(longitude);//控制台显示数据
println(latitude);//控制台显示数据
saveFrame("mapnn.jpg");
}

void serialEvent(Serial myPort) {
// read the serial buffer:
   String myString = myPort.readStringUntil('\n');
// if you got any bytes other than the linefeed, parse it:
if (myString != null) {
    print(myString);
    parseString(myString);
}
}

void getRMC(String[] data) {
    latitude = minutesToDegrees(float(data));
    longitude = minutesToDegrees(float(data));
}

float minutesToDegrees(float thisValue) {
   int wholeNumber = (int)(thisValue / 100); // get the integer portion of the degree measurement:
   float fraction = (thisValue - wholeNumber*100 ) / 60;// get the fraction portion, and convert from minutes to a decimal fraction:
   float result = wholeNumber + fraction; // combine the two and return it:
return result;
}

void parseString (String serialString) {

String items[] = (split(serialString, ','));// split the string at the commas:
if (items.equals("A")) { // if the first item in the sentence is the identifier, parse the rest
      getRMC(items);//get latitude,longitude
   }
}
程序运行结果如下图,GPS实时位置.JPG,随着GPS位置的移动,图中的点也会在不停变化。
这只是一个实验,实用意义不大,因为你不可能为了看地图而背着PC到处跑(哪怕是笔记本)的,不过可以提供一个思路:
如果把Arduino和PC机之间用无线模块(如Xbee等)连接起来,就可以在小范围对某个东西进行实时位置监控。比如:你家后院有个几百平方米的大院子或草坪,给你们家的乌龟背着个Arduino,你就可以在电脑上看见它到哪玩去了,不会走丢哦!{:soso_e120:}
程序主体基本都是借鉴别人的,感谢开源社区提供如此丰富的素材。

fangtaonj 发表于 2013-3-28 18:52:14

沙发!楼主谦虚,作品一个又一个,代码一大段一大段的向外发!还说不会编程呵呵!

fangtaonj 发表于 2013-3-28 18:52:49

极好的教材,等我搞懂你的第一个程序再来琢磨这个!

龙翔竞天 发表于 2013-5-24 22:14:30

乖乖,这个可以的。一定要再让它飘起来

qczhao_10 发表于 2013-6-12 09:13:51

楼主真厉害,佩服

piaozhiling 发表于 2013-7-28 12:59:57

楼主高手啊,也是好人那。小弟谢谢了,

tanbocandy 发表于 2013-9-6 01:26:53

这个必定火

larry.wong 发表于 2014-2-15 23:22:02

高手众多!

杭电屌丝 发表于 2014-4-20 20:44:54

高手啊,,,
页: [1]
查看完整版本: Processing处理GPS数据实验(更新,用谷歌地图定位)