极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 18426|回复: 8

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

[复制链接]
发表于 2013-3-28 16:14:26 | 显示全部楼层 |阅读模式
本帖最后由 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程序:
  1. #include <LiquidCrystal.h>  
  2. #include <SoftwareSerial.h>
  3. #define DADOS_LEN 100
  4. #define IDLEN 6
  5. #define TEMPLEN 11
  6. #define GPRMC 0
  7. #define GPGGA 1
  8. char data[DADOS_LEN]; //buffer for GPS data
  9. byte conta=0; //variavel auxiliar para contar
  10. char* idnmea[] = {
  11.   "$GPRMC","$GPGGA"}; //IDs dos NMEA que vou utilizar
  12. byte verificador[]= {
  13.   0,0}; //variavel auxiliar para verificar ID NMEA
  14. byte indice[12]; //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.
  15. byte contindice=0; //variavel auxiliar de controle usada na variavel indice[];
  16. byte menu=0; // Menu do LCD: 0-key RIGHT, 1-key UP, 2-key DOWN, 3-key LEFT, 4-key SELECT
  17. char tempmsg[TEMPLEN]; //variavel temporaria auxiliar para guarda o valor de um dado extraido do GPS.
  18. SoftwareSerial nss(3, 2);
  19. LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
  20. int  adc_key_val[5] ={
  21.   30, 150, 360, 535, 760 }; //valores do divisor de tens&atilde;o do teclado do LCD Shield
  22. #define NUM_KEYS 5 //numero de teclas do teclado
  23. int adc_key_in; //valor da entrada analogica do teclado
  24. byte key=-1; //tecla pressionada
  25. byte oldkey=-1; //tecla pressionada anteriormente

  26. void setup()
  27. {
  28.   Serial.begin(9600);
  29.   nss.begin(9600);
  30.   lcd.clear(); //Clear LCD
  31.   lcd.begin(16,2);
  32.   lcd.print("Arduino.cc");
  33.   lcd.setCursor(0, 2); //set cursor on LCD at col 0 and row 2
  34.   lcd.print("Arduino GPS");
  35.   clearBuffer(); //clear buffer for GPS (databuffer)
  36.   clearTemp(); //clear data for GPS (tempmsg)
  37.   //Serial.begin(9600); //Inicia UART para comunicar com módulo GPS
  38.   delay(3000);
  39. }

  40. void loop(){   
  41.   while(nss.available())
  42.   {//if serial port available
  43.     data[conta] = nss.read(); //Read a byte of the serial port
  44.     if(data[conta]==13)
  45.        {  //If the received byte is = to 13, end of transmission
  46.             verificador[GPRMC]=0; //verifies idnmea[0] ($GPRMC)
  47.            //verificador[GPGGA]=0;//verifies idnmea[1] ($GPGGA)
  48.             for(byte i=1;i<=IDLEN;i++)
  49.                 { //checking the ID NMEA of string received
  50.                    if(data[i]==idnmea[GPRMC][i-1])
  51.                      {        //Verifies that is $GPRMC
  52.                       verificador[GPRMC]++; //increases 1
  53.                       }

  54.                  }
  55.              if(verificador[GPRMC]==IDLEN){ // if the line received is $GPRMC
  56.                //A line string of GPRMC has 11 "," Divided into 12 data
  57.               //exemplo: $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
  58.              //            0      1   2    3    4     5    6    7    8     9     10    11
  59.             // we interesting: 2-timestamp (UTC), 3-latitude, 4-North/South, 5-Longitude, 6-East/West,7-Speed in knots,9-date stamp
  60.                     contindice = 0;
  61.                     indice[contindice] = 1; //data[] inicia no caracter 1
  62.                     contindice++;
  63.              for(byte i=1; i<DADOS_LEN;i++){ //crosses every line data[] identifying where each value of GPS
  64.           if(data[i]==','){ //found the final of a data
  65.             indice[contindice] = i+1;
  66.             contindice++;
  67.           }
  68.         }
  69.         adc_key_in = analogRead(0); //verifica entrada analogica do teclado
  70.         key = get_key(adc_key_in); //interpreta valor da entrada analogica
  71.         if (key != oldkey){ //verifica se o valor encontrado é diferente do valor anterior
  72.           delay(50);                // faz um delay para o debounce
  73.           adc_key_in = analogRead(0);   
  74.           key = get_key(adc_key_in);                        // interpreta
  75.           if (key != oldkey){                        //verifica se é diferente
  76.             oldkey = key; //atualiza oldkey
  77.             lcd.clear(); //clear LCD
  78.             if (key >=0){ //se alguma tecla foi pressionada
  79.               menu = key; //atualiza o menu
  80.             }
  81.           }
  82.         }
  83.         Serial.print(datastream(2));
  84.          Serial.print(",");
  85.          Serial.print(datastream(1));
  86.          Serial.print(",");
  87.          Serial.print(datastream(3));
  88.          Serial.print(",");
  89.          Serial.print(datastream(4));
  90.          Serial.print(",");
  91.          Serial.print(datastream(5));
  92.          Serial.print(",");
  93.          Serial.print(datastream(6));
  94.          Serial.print(",");
  95.          Serial.print(datastream(7));
  96.          Serial.print(",");
  97.          Serial.print(datastream(8));
  98.          Serial.print(",");
  99.          Serial.print(datastream(9));
  100.          Serial.print(",");
  101.          Serial.print(datastream(10));
  102.          Serial.print(",");
  103.          Serial.print(datastream(11));
  104.          //Serial.print("*");
  105.          Serial.println(" ");
  106.         switch(menu){
  107.           //show on  LCD the key pressed
  108.         case 4: //SELECT 4-key
  109.           lcd.setCursor(1,0); //move cursor to LCD  column 1 row 0
  110.                 lcd.print("True course"); //print text to LCD
  111.                 lcd.setCursor(1,1); //move cursor to LCD  column 1 row 1
  112.                 lcd.print(datastream(7)); //print text to LCD
  113.           break;            
  114.         case 3: //LEFT 3-key
  115.           lcd.clear();
  116.           lcd.setCursor(0,0);
  117.           lcd.print("Lat:");
  118.           lcd.print(datastream(4));
  119.           lcd.print(" ");
  120.           lcd.print(datastream(3)); // datastream(byte)
  121.           lcd.setCursor(0, 1);
  122.           lcd.print("Lon:");
  123.           lcd.print(datastream(6));
  124.           lcd.print(" ");
  125.           lcd.print(datastream(5));
  126.           break;
  127.         case 1: //UP   1-key
  128.           lcd.setCursor(1,0);
  129.           lcd.print("Date:");
  130.           lcd.print(datastream(9));
  131.           lcd.setCursor(1,1);
  132.           lcd.print("Time:");
  133.           lcd.print(datastream(1));
  134.           break;
  135.         case 2: //DOWN  2-key
  136.           lcd.setCursor(1,0);
  137.           lcd.print("Speed:");
  138.           lcd.print(datastream(7));
  139.           lcd.setCursor(1,1);
  140.           lcd.print("knots");
  141.           break;
  142.         }

  143.       }

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

  150.   }
  151. }
  152. void clearBuffer(){
  153.   for (byte i=0;i<DADOS_LEN;i++){       // clear variavel (buffer) received GPS data
  154.     data[i]=' ';
  155.   }  
  156. }
  157. void clearTemp(){
  158.   for(byte i=0;i<TEMPLEN;i++)
  159.     tempmsg[i]=' ';
  160. }

  161. char* datastream(byte inicio){
  162.   /*
  163.   Receive Datastream from GPS devices,then convert to Data We can read directly
  164.    remenber that: 2-timestamp (UTC), 3-latitude, 4-North/South, 5-Longitude, 6-East/West,7-Speed in knots,9-date stamp
  165.    */
  166.   clearTemp();
  167.   byte i;
  168.   byte fim = indice[inicio+1]-2;
  169.   inicio = indice[inicio];
  170.   for(i=0;i<=(fim-inicio);i++){
  171.     tempmsg[i] = data[inicio+i];
  172.   }
  173.   tempmsg[i] = '\0';
  174.   return tempmsg;
  175. }

  176. // Convert ADC value to key number
  177. byte get_key(unsigned int input)
  178. {
  179.   int k=menu;
  180.   for (byte i = 0; i < NUM_KEYS; i++){
  181.     if (input < adc_key_val[i]){
  182.       k=i;
  183.       return k;
  184.     }
  185.   }
  186.   return k;

  187. }

复制代码


   其次:用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()[0]; 一行,需将方括号[]中的数字换成你的Arduino接PC机的串口实际值,并注意修改你用的GPS波特率(我的是9600bps)。
Processing程序:
[pre lang="rocessing" line="1" file="rocessing"]/*
  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()[0];
  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[0].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[1]);
  // 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[2].equals("A")) {
    latitude = minutesToDegrees(float(data[2]));

    northSouth = data[3];
    longitude = minutesToDegrees(float(data[4]));
    eastWest = data[5];
    heading = float(data[7]);
    int date = int(data[8]);
    // 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);
}
[/code]


继续实验,是否能在地图上定位我们的位置呢?在网上搜索了一下,有人做了一个调用谷歌地图的库函数,这里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
运行如下代码:
[pre lang="rocessing" line="1"]
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();
}
[/code]
运行程序,得到map.jpg,即为你想得到的底图,可以用来做定位的地图了。同时记录下processing下部状态窗口显示的上、下顶点位置,后面程序要用的。
下面是processing用GPS定点后显示位置的主程序:
[pre lang="processing" line="1"]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;          // Latitude  north,前面计算出来的上定点数据,看截图
float mapGeoBottom = xx.xxxxxx;          // Latitude  south.前面计算出来的下定点数据,看截图
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()[0];// 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[2]));
    longitude = minutesToDegrees(float(data[4]));
}

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2013-3-28 18:52:14 | 显示全部楼层
沙发!楼主谦虚,作品一个又一个,代码一大段一大段的向外发!还说不会编程呵呵!
回复 支持 反对

使用道具 举报

发表于 2013-3-28 18:52:49 | 显示全部楼层
极好的教材,等我搞懂你的第一个程序再来琢磨这个!
回复 支持 反对

使用道具 举报

发表于 2013-5-24 22:14:30 | 显示全部楼层
乖乖,这个可以的。一定要再让它飘起来
回复 支持 反对

使用道具 举报

发表于 2013-6-12 09:13:51 | 显示全部楼层
楼主真厉害,佩服
回复 支持 反对

使用道具 举报

发表于 2013-7-28 12:59:57 | 显示全部楼层
楼主高手啊,也是好人那。小弟谢谢了,
回复 支持 反对

使用道具 举报

发表于 2013-9-6 01:26:53 | 显示全部楼层
这个必定火
回复 支持 反对

使用道具 举报

发表于 2014-2-15 23:22:02 | 显示全部楼层
高手众多!
回复 支持 反对

使用道具 举报

发表于 2014-4-20 20:44:54 | 显示全部楼层
高手啊,,,
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-24 17:51 , Processed in 0.043660 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表