子產品采用 Quectel EC20 CE FILG (4G+GPS),這裡我隻對軟體部分實作GPS資料的采集、資料幀解析和GPS漂移點過濾的一些方法和要點進行總結。首先,上效果圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR1keVR0TyUFVPpHOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3IjNxAjM1QTMwIDMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
需求: 采集GPS位置資訊,定時上傳GPS資料到平台(平台上展示gps錨點和軌迹資訊部分,不再本文讨論範圍)
1、GPS資料采集前的準備工作
GPS設定,可以通過調用AT指令,依次執行以下操作,
"AT+QGPSCFG=\"outport\",\"usbnmea\""
"AT+QGPS=1"
"AT+QGPS?"
"AT+QGPSLOC=?"
筆者本地使用microcom工具,指令如下:
// 通過管道執行shell指令并傳回結果
static bool executeCMD(const char *cmd, char *result)
{
char buf_ps[2048];
char ps[1024]={0};
FILE *ptr = NULL;
strcpy(ps, cmd);
ptr=popen(ps, "r");
memset(result,0,strlen(result));
if(ptr!=NULL)
{
while(fgets(buf_ps, 1024, ptr)!=NULL)
{
strcat(result, buf_ps);
if(strlen(result)>1024)
break;
}
pclose(ptr);
ptr = NULL;
c_debug("cmd %s exec result is :\n%s",cmd,result);
return true;
}
else
{
c_debug("thread cmd open failed");
return false;
}
}
// 初始化GPS子產品
void initGPS(char* cmd){
char buff[1024] = {0};
sprintf(buff,"echo -e \"%s\\r\\n\" | microcom -s 115200 -t 2000 /dev/ttyUSB2",cmd);
executeCMD(buff,result);
}
另外說明, EC20 CE FILG 子產品包含4個驅動,/dev/ttyUSB2 序列槽用于接收AT指令并輸出執行結果
在執行初始化GPS操作前,可以監聽 /dev/ttyUSB2 口
cat /dev/ttyUSB2
另外,我們查詢4G子產品相關詳細,如
“AT+CSQ” ==》 查詢信号強度
“AT+CIMI” ==》 SIM卡檢測
...
等等,相關執行結果都可以在 /dev/ttyUSB2 口檢視到
2、檢視gps資料
gps資料檢視有以下兩種方式:
(1)使用AT指令檢視:
AT+QGPSLOC= 2 ;
備注:查詢到的經緯度格式 format: (-)dd.ddddd,(-)ddd.dddd ,即以度為機關
除了2 之位,還有其他的參數可設定,具體,可檢視子產品操作相關說明文檔
(2)通過監聽 /dev/ttyUSB1 口可以檢視,可以看到有大量的GPS資料從此端口吐出(output)
cat /dev/ttyUSB1
這裡,我們如果需要編寫程式從/dev/ttyUSB1 口循環讀取gps資料,可以參考Linux下C語言序列槽程式設計通訊示例,這裡不再過多介紹。
這裡有個的坑需要注意:預設輸出的經緯度資訊 GPGGA中,使用格式 dddmm.mmmmmm ,一定要進行響應的轉換
3、經緯度轉換
采集的原資料為WGS-84格式,如果我們使用百度地圖的API,需要轉換成BD09,這裡提供一套網上的轉換方法,項目測試可用。
// .h
struct gps_position_t {
int32_t lat; /*< Latitude, expressed as degrees * 1E7*/
int32_t lon; /*< Longitude, expressed as degrees * 1E7*/
double lat_f;
double lon_f;
};
class QGCTransform
{
public:
QGCTransform();
/*WGS-84 to GCJ-02*/
//double PI;
double a;
double ee;
gps_position_t gps_t;
void Transform();
void Transform(double lat, double lon);
void bd_encrypt(double gg_lat, double gg_lon, double &bd_lat, double &bd_lon);
void bd_decrypt(double bd_lat, double bd_lon, double &gg_lat, double &gg_lon);
private:
bool OutOfChina(double lat, double lon);
double TransformLat(double x, double y);
double TransformLon(double x, double y);
};
// .cpp
#define PI (3.14159265358979324)
//其中 bd_encrypt 将 GCJ-02 坐标轉換成 BD-09 坐标, bd_decrypt 反之
const double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
void QGCTransform::bd_encrypt(double gg_lat, double gg_lon, double &bd_lat, double &bd_lon)
{
double x = gg_lon, y = gg_lat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
bd_lon = z * cos(theta) + 0.0065;
bd_lat = z * sin(theta) + 0.006;
}
void QGCTransform::bd_decrypt(double bd_lat, double bd_lon, double &gg_lat, double &gg_lon)
{
double x = bd_lon - 0.0065, y = bd_lat - 0.006;
double z = sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);
gg_lon = z * cos(theta);
gg_lat = z * sin(theta);
}
QGCTransform::QGCTransform(){
a = 6378245.0;
ee = 0.00669342162296594323;
}
/*WGS-84 to GCJ-02*/
bool QGCTransform::OutOfChina(double lat, double lon){
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
}
double QGCTransform::TransformLat(double x, double y){
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * qSqrt(qAbs(x));
ret += (20.0 * qSin(6.0 * x * PI) + 20.0 * qSin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * qSin(y * PI) + 40.0 * qSin(y / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * qSin(y / 12.0 * PI) + 320 * qSin(y * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
double QGCTransform::TransformLon(double x, double y)
{
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * qSqrt(qAbs(x));
ret += (20.0 * qSin(6.0 * x * PI) + 20.0 * qSin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * qSin(x * PI) + 40.0 * qSin(x / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * qSin(x / 12.0 * PI) + 300.0 * qSin(x / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
void QGCTransform::Transform(){
double lat_s=gps_t.lat * 1E-7;
double lon_s=gps_t.lon * 1E-7;
Transform(lat_s, lon_s);
}
void QGCTransform::Transform(double lat_s, double lon_s){
gps_t.lat_f = lat_s;
gps_t.lon_f = lon_s;
gps_t.lat = lat_s * 1E7;
gps_t.lon = lon_s * 1E7;
if (OutOfChina(lat_s, lon_s ))
{
return;
}
double dLat = TransformLat(lon_s - 105.0, lat_s - 35.0);
double dLon = TransformLon(lon_s - 105.0, lat_s - 35.0);
double radLat = lat_s / 180.0 * PI;
double magic = qSin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = qSqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * qCos(radLat) * PI);
gps_t.lat = (lat_s + dLat) * 1E7;
gps_t.lon = (lon_s + dLon) * 1E7;
gps_t.lat_f = lat_s + dLat;
gps_t.lon_f = lon_s + dLon;
}
4、漂移點過濾
關于漂移點過濾的邏輯,我們可以簡單使用如下方法處理,大緻提升采集資料的可靠性(經度更多依賴于硬體本身,這裡我們隻讨論軟體過濾輔助提高經度的方法)
(1)過濾無效點。 $GPRMC <2>狀态, A 為有效位置, V為非有效接收警告
(2)采集樣本點。前10個點不上傳,用于判斷裝置的運動模式,如果超過6個以上采集到速度為0,視為靜止,否則可以視為運動模式處理。
(3)靜止模式下,前10個點有兩中處理方式,第一種直接将最後一個有效點作為基準點處理;第二種,計算10個樣本點的離散中心,作為基準點;
(4)運動狀态下,則采用下幾個步驟進一步過濾,然後以最後一個有效點作為運動狀态的基準點上傳資料;
采用大速度過濾原則:通過GPVTG擷取地面速度,如果速度大于130Km/h,則過濾
采用大距離過濾原則:通過計算前後采集相鄰兩采集點之際的距離,如果距離超過50m則過濾(可調整)
采用大加速度過濾原則:目前一般轎車10秒内可從零加速到100km/h,平均加速度為2.778m/s^2,即大約0.28g,最大加速度可達到0.6g左右。這裡通過計算前後gps位置點的加速度,如果超過0.6g,則過濾掉。
另,具體開發遇到坑比較多,祝好運🤞
參考:一篇關于GPS定位寫得最詳實清晰的文章之一