1. 程式人生 > >巧妙解決百度地圖加偏糾偏問題

巧妙解決百度地圖加偏糾偏問題

麗水市汽車運輸集團股份有限公司資訊中心苟安廷

當我們興匆匆地把GPS裝置傳來的經緯度座標標記到電子地圖上時,發現地圖上的位置和實際位置相差甚遠,這就是天朝上國特有的地圖加偏,以安全之名,行掩耳盜鈴之事,給我們的開發帶來了不必要的麻煩,至於加偏的原因,大家百度一下,網上一大堆,這裡就不廢話了。除GPS裝置本身加偏外,電子地圖上的座標也不是真實的,要想正確顯示到地圖上,還必須將收到的加偏座標(俗稱火星座標)換算到地圖對應的座標,也就是我們常說的糾偏,而國內用的比較多的地圖主要是谷歌和百度,前者有具體的控制元件,比較好解決,後者就麻煩了,本人這段時間為此事折騰得不輕,但功夫不費有心人,總算比較完美地解決了,故整理一下,給還在或將要受此折騰的人蔘考。

和大多數人一樣,碰到這個問題時,首先想到了百度,總結了百度結果,主要有以下三個方法:

1.使用控制元件

如果和谷歌地圖一樣,有一個控制元件,直接傳入GPS座標得到地圖座標,那就省事了,通過正常途徑獲取控制元件的話,我們這種小公司還沒有那個實力,而非正常途經的控制元件或演算法貌似還沒有洩漏出來,即使洩漏了,你也不敢放心使用,萬一有一天,被以洩漏“郭嘉咪咪”的名義請你去喝茶就得不償失了,因此,本方法基本放棄。

2.百度地圖介面

百度網站提供了介面,只要通過http傳入GPS座標引數就可以獲得對應的地圖座標,該方法優點是方便準確,不足也很明顯,受網速、百度伺服器等影響,處理大量併發業務時力不從心。

3.資料庫

所謂的加偏,就是將真實座標加上一定的偏移量,而這個偏移量又不是線性的,不同地區偏移不一樣,但同一地區偏移量卻差不多,因此,有人就使用了個暴力破解的方法,將全國按GPS座標分成很多小塊,然後查出每個小塊的偏移量,並儲存到資料庫裡面,需要糾偏時,先根據GPS座標取出對應區域的偏移量,反算出地圖座標。優點:本地執行,速度快,缺點:中國太大了,存放區域的記錄有幾千萬條,不僅佔用了大量儲存空間,檢索速度也大受影響,更要命的是,網上也很難找到一個完整的資料庫,有些網站說有完整的,但要Money,提到Money就不親熱了,畢竟我們是個人或小公司嘛,錢不是問題,問題是沒錢。

貌似進入了一個死衚衕,但我們仔細一想,資料庫方法不錯,但有點浪費,因為我們面向的客戶大多是某區域的,而且,車輛行駛的路線相對是固定的(尤其是客運班車),塔克拉瑪干沙漠或者居民小區樓頂的座標對於我們來說,貌似沒有多少意義,理論上,我們需要的僅僅是客戶車輛行駛區域的座標就可以了,車輛能到達的區域(通常是公路)相對整個國土面積來說,太小了,因此,有這麼一個輕量級的資料庫是不是就完美了呢?本文正是基於這一思路,將方法

2和方法3結合起來,巧妙解決了百度地圖糾偏問題。

基本原理:收到GPS座標後,首先計算出該座標所屬的區域,然後再從本地資料庫查詢該區域的偏移量,如果查詢到,直接和該偏移相加得到地圖座標,如果沒有查詢到,則從百度網站介面查詢,並和GPS座標相減得到偏移,將本偏移存放到本地資料庫,然後直接返回從百度介面得到的地圖座標。換句話說,我們自己根據車輛使用過的座標,構建了一個輕量級的資料庫,使用一段時間後,我們會發現,絕大部分資料都是從本地資料庫獲取的,資料量也就區區幾十萬條而已。

我們分步驟完成這一思路。

第一步:劃分區域的演算法

GPS收到的座標(經度和緯度兩個方向)是百萬分之一度,整數,如果每百萬分之一度作為一個區域,那麼,基本上所有的GPS座標都位於不同的區域了,這麼小的區域沒有什麼意義。我們知道,一度等於60分,一分等於60秒,那麼,一度就是3600秒了,而經緯度中,一秒對應的距離大約31米,如果我們按秒劃分區域,應該是可以接受的,也就是說,用大約31米邊長的正方形表示一個區域,落在該區域的座標我們認為偏移量是一樣的。因此,我們首先要把GPS座標轉換一下,用一個精確到“秒”的數字來標記所屬的區域。假如某經度為120.123456度,我們將小數部分轉換成“秒”:0.123456×3600=444.4416,精確到秒,就是444秒,後面的小數部分扔掉了,因此,該GPS經度位於經度為120444秒的某區域中,該區域的緯度演算法一樣。在資料庫檢索中,整數速度遠遠大於浮點數,我們想辦法將120444秒用一個整數特徵值來表示,我們知道,GPS返回的座標為百萬分之一度,因此,我們將“度”乘以一百萬,再把秒加上去就可以表示了,120.123456度轉換成區域經度的數字為120000444,這個數字看起來怪怪的,前面部分表示度,後面部分表示秒,其實也無所謂,反正就是一個區域的唯一標記的特徵值而已。為此,我們寫一個方法來計算(注意,引數都是百萬分之一度)。

參考程式碼如下:

publicint GetAreaPostion(int GpsCoordinate)

{

//計算""的部分

int nDegree = GpsCoordinate / 1000000 * 1000000;

//計算度後面小數部分

int nSecond = (int)(0.000001 * (GpsCoordinate - nDegree) * 3600);

//兩者重新相加

return nDegree + nSecond;

}

第二步:建立資料庫表

我們需要在資料庫裡面建立一個表,記錄每個區域的偏移量,一個區域包含精確到秒的經度和緯度兩列,是唯一的,因此,為直觀,我們不妨用中文的“秒經度”、“秒緯度”兩列表示,並設定為主鍵、聚族索引,再增加“經度偏移”、“緯度偏移”、“建立時間”三列,之所以要增加“建立時間”欄位,是考慮到時間久了,最好更新一下,比如,半年前的資料最好重新從百度網站取,考慮到速度問題,該欄位也需要建立索引。資料庫設計介面如下:

第三步:將接收到的GPS座標轉換成對應的區域

座標包含經度、緯度兩部分,因此,我們用Point表示,轉換辦法參考如下:

            MapCorrect map=newMapCorrect();

            Point ptArea = new Point(0, 0);

            ptArea.X = map.GetAreaPostion(Lng);

            ptArea.Y = map.GetAreaPostion(Lat);

第四步:建立資料庫訪問的方法

既然要訪問資料庫,必然要提供操作資料庫的方法,資料庫操作不是本文的重點,大家也都會操作,這裡我們建立一個名稱為DB的類用於操作資料庫,該類主要提供兩個靜態方法:

public staticDataRow GetValueDataRow(string p_strSql)

該方法執行傳入的SQL語句,並將資料庫返回的資料集第一行返回,如果資料庫沒有資料返回,該方法返回null

public staticbool ExecSql(string p_strSql)

該方法執行指定的SQL語句,如果執行錯誤,返回false,執行成功,返回true

第五步:從資料庫查詢偏移

為了加快速度和程式碼更清晰,建議使用儲存過程,儲存過程程式碼如下(忽略半年前的資料)

CREATE PROCEDUREGetOffset

    @Lng INT,  --精確到秒的經度

    @Lat INT   --精確到秒的緯度

AS

BEGIN

    SET NOCOUNT ON;

    SELECT 經度偏移,緯度偏移

    FROM dbo.糾偏區域 WITH(NOLOCK)

    WHERE 秒經度=@Lng AND 秒緯度=@LatAND建立時間>DATEADD(dd,-180,GETDATE())

END

第六步:根據查詢結果進行處理

如果查詢結果中已經有偏移了,直接將偏移和GPS座標相加,否則,從百度網站查詢,並計算偏移然後儲存到資料庫。

public Point GetMapCoordinate(Point GpsCoordinate)

        {

            string strSql = string.Format("EXEC GetOffset @Lng ={0},@Lat={1}",

                GetAreaPostion(GpsCoordinate.X),

                GetAreaPostion(GpsCoordinate.Y));

            DataRow row = DB.GetValueDataRow(strSql);

            Point ptMap = new Point(0, 0);

            if (row == null) //從百度網站查詢

            {

                ptMap = GetFromBaidu(GpsCoordinate);

            }

            else//直接返回

            {

                ptMap.X = GpsCoordinate.X + (int)row["經度偏移"];

                ptMap.Y = GpsCoordinate.Y + (int)row["緯度偏移"];

            }

            return ptMap;

        }

這裡,我們需要完成從百度查詢座標的方法,百度網站返回的是JSON資料,JSON很強大,操作方法很多,但這裡得到的結果卻是簡單的一句話:

{"error":0,"x":"MTE5LjE1NzE5NzEwMDc5","y":"MjguMzI0NzUzMTI4NDEz"}

顯然,error表示錯誤資訊,0表是無錯誤,後面的兩個是地圖座標的經度和緯度,進行了Base64編碼,格式是固定的,為了使用這麼簡單的一個JSON而引入其他專案顯然沒必要,我們直接用簡單的字串分解一下就可以了,程式碼較多,這裡就不列出原始碼了(文末會附下載地址),得到地圖座標後,一定要計算偏移,並更新到資料庫中,更新資料庫的儲存過程參考如下:

CREATE PROCEDUREUpdateOffset

    @Lng INT,     --區域秒經度

    @Lat INT,     --區域秒緯度

    @OffLng INT,  --經度偏移

    @OffLat INT       --緯度偏移

AS

BEGIN

    SET NOCOUNT ON;

    UPDATE dbo.糾偏區域 SET 經度偏移=@OffLng,緯度偏移=@OffLat,建立時間=GETDATE()

    WHERE 秒經度=@Lng AND 秒緯度=@OffLat

    IF @@ROWCOUNT=0

    BEGIN

       INSERT INTO dbo.糾偏區域(秒經度,秒緯度,經度偏移,緯度偏移,建立時間)

       VALUES (@Lng,@Lat,@OffLng,@OffLat,GETDATE())

    END

END

使用儲存過程,不僅提高了速度,減少了網路流量,還使客戶端程式碼更加清晰簡潔。以上僅僅是實現的基本原理,實際使用時,應該考慮多執行緒、異常處理等等實際情況。

題外話:

對於一個GPS軟體,除了能在地圖上正確顯示車輛位置外,還應該顯示當前位置對應的中文描述,如“浙江省杭州市滬杭高速”,也就是根據座標查詢對應的地理位置資訊描述,即反向地理編碼查詢,百度地圖提供了相應的介面(介面地址:http://developer.baidu.com/map/carapi-2.htm),我們可以用上面同樣的辦法,在本地建立區域快取,避免每次都從網站讀取,另外,谷歌地圖提供的查詢更詳細和準確,二者均有使用限制,百度每個金鑰每天限制5000次查詢,而註冊一個賬號,可以申請最多20個金鑰,賬號註冊是免費的,其餘你懂的,後者按IP限制,每個IP每天最多查詢2500次,而GPS伺服器往往都是固定IP,對於業務初期,2500次杯水車薪。為解決這一問題,我們可以在快取百度地理位置資訊時,將GPS原始座標也儲存起來,然後另外開一個程式,通過從谷歌查詢地理位置資訊來對快取中的位置資訊描述進行更新,也就是說,下次再使用這一區域的地理資訊名稱時,得到的就是谷歌解析的名稱了,如何突破谷歌的限制呢?你可以把軟體COPY到一個撥號上網的地方(如家裡),超過限制後,重新撥號,當然,你可以手工斷線和重撥,也可以寫一段程式碼實現自動斷線和重撥,等過一段時間,客戶車輛大部分位置資訊都快取到本地後,就可以直接執行在伺服器上了。

附原始碼下載地址(演示時,為方便使用,可以輸入120.123456,也可以輸入120123456):