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çã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ã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:}
程序主体基本都是借鉴别人的,感谢开源社区提供如此丰富的素材。 沙发!楼主谦虚,作品一个又一个,代码一大段一大段的向外发!还说不会编程呵呵! 极好的教材,等我搞懂你的第一个程序再来琢磨这个! 乖乖,这个可以的。一定要再让它飘起来 楼主真厉害,佩服 楼主高手啊,也是好人那。小弟谢谢了, 这个必定火 高手众多! 高手啊,,,
页:
[1]