天天看點

關于GDAL讀寫Shp亂碼的問題總結

總結下在使用GDAL讀取Shp檔案時遇到亂碼的問題。

目錄

  • 1. 正文
    • 1.1. shp檔案本身的編碼的問題
    • 1.2. 設定讀取的編碼方式
      • 1.2.1. GDAL設定
      • 1.2.2. 解碼方式
      • 1.2.3. 其他
  • 2. 參考

最近在使用GDAL讀寫Shp格式中的屬性字段的時候也遇到了中文亂碼的問題,總結下自己遇到的情況。

應該是由于shp格式加入了對寬字元的支援,是以導緻有段時間的shp檔案和ArcGIS是存在不比對的問題,是以在網上搜尋資源的時候遇到了大量的關于ArcMap顯示shp屬性表出現亂碼的問題。現在的shp格式的檔案應該已經穩定下來了,新添加了一個.cpg的檔案,裡面儲存着屬性表的編碼格式:

關于GDAL讀寫Shp亂碼的問題總結

圖1-1:shp格式的.cpg檔案

從ArcGIS10.2開始,隻要是屬性表編碼與.cpg檔案記錄的編碼方式一緻,就不會再有顯示亂碼的問題。網上查詢到的修改系統資料庫的方法,我在ArcGIS10.2中試過,似乎已經不再起效了。

那麼對于沒有.cpg或者的情況,應該可以看屬性表.dbf檔案。如果編碼方式正确,這個檔案用文本編輯器打開是可以看到正常的中文的:

關于GDAL讀寫Shp亂碼的問題總結

圖1-2:shp格式的.dbf檔案

在正常顯示中文情況下,可以檢視下檔案的編碼方式:

關于GDAL讀寫Shp亂碼的問題總結

圖1-3:檢視編碼方式

當然,如果遇到亂碼,可以嘗試用别的編碼方式打開,這樣你就能知道屬性表具體是什麼編碼了。對于國内的情況來說,隻有ANSI編碼和UNICODE編碼兩種:其中簡體中文系統中ANSI編碼就是GB2312編碼;UTF-8是UNICODE編碼的一種具體實作。

可以通過全局設定函數CPLSetConfigOption(),來配置讀取Shp檔案的讀取編碼。例如對于簡體中文系統中ANSI編碼,可以設定為GBK:

CPLSetConfigOption("SHAPE_ENCODING","GBK");
           

上面這種方式是全局設定的,如果想設定單個檔案的編碼方式也是可以的。例如,打開一個矢量檔案讀取為UTF-8的資料集:

char** ppszOptions = NULL;
ppszOptions = CSLSetNameValue(ppszOptions, "ENCODING", "UTF-8");
GDALDataset *poDS = (GDALDataset*)GDALOpenEx(filePath.c_str(), GDAL_OF_VECTOR, NULL, ppszOptions, NULL);
           

網上提供的解決方案都是将編碼方式設定為空[1],這種方式應該更具有通用性,起碼我這裡讀取GBK和UTF-8格式的Shp的格式都是可以的:

CPLSetConfigOption("SHAPE_ENCODING","");
           

如果讀取出來的字段屬性仍然是亂碼,就應該考慮字元串的解碼問題,就是擷取的字段屬性字元串沒有正确的解碼出來。例如讀取UTF-8的Shp檔案的屬性字段:

OGRFeature *poFeature;
while ((poFeature = poLayer->GetNextFeature()) != NULL)
{
    OGRGeometry *pGeo = poFeature->GetGeometryRef();
    OGRwkbGeometryType pGeoType = pGeo->getGeometryType();

    //		
    OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
    int n = poFDefn->GetFieldCount(); //獲得字段的數目,不包括前兩個字段(FID,Shape);
    for (int iField = 0; iField <n; iField++)
    {
        //輸出每個字段的值
        //cout << poFeature->GetFieldAsString(iField) << "    ";
        cout << UTF8_To_string(poFeature->GetFieldAsString(iField)) << "   ";			
    }
    //cout << endl;   

    OGRFeature::DestroyFeature(poFeature);
}
           

預設情況下,cout是無法正确列印輸出UTF-8字元編碼的,通過UTF8_To_string這個函數,将UTF-8編碼的字元串轉換成本地ANSI編碼,也就是GBK編碼字元串,就可以正确輸出顯示了。附帶一下兩者的轉換函數[2]:

// UTF8轉std:string
// 轉換過程:先将utf8轉雙位元組Unicode編碼,再通過WideCharToMultiByte将寬字元轉換為多位元組。
std::string UTF8_To_string(const std::string& str) 
{ 
    int nwLen = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0); 
    wchar_t* pwBuf = new wchar_t[nwLen + 1];    //一定要加1,不然會出現尾巴 
    memset(pwBuf, 0, nwLen * 2 + 2); 
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), pwBuf, nwLen); 
    int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL); 
    char* pBuf = new char[nLen + 1]; 
    memset(pBuf, 0, nLen + 1); 
    WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);

    std::string strRet = pBuf; 

    delete []pBuf; 
    delete []pwBuf; 
    pBuf = NULL; 
    pwBuf = NULL; 

    return strRet; 
} 

// std:string轉UTF8
std::string string_To_UTF8(const std::string& str) 
{ 
    int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0); 
    wchar_t* pwBuf = new wchar_t[nwLen + 1];    //一定要加1,不然會出現尾巴 
    ZeroMemory(pwBuf, nwLen * 2 + 2); 
    ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen); 
    int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL); 
    char* pBuf = new char[nLen + 1]; 
    ZeroMemory(pBuf, nLen + 1); 
    ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL); 

    std::string strRet(pBuf); 

    delete []pwBuf; 
    delete []pBuf; 
    pwBuf = NULL; 
    pBuf  = NULL; 

    return strRet; 
} 
           

還有個值得注意的問題就是Shp格式的屬性字段名稱的長度最大隻能支援10個字元。如果采用UTF-8編碼,可能用不了幾個中文字元就被截斷了,這個時候屬性字段名稱也可能存在亂碼。

[1] GDAL/OGR 1.9.0擷取shp檔案中中文字段值和屬性值亂碼檔案解決

[2] UTF8與std:string互轉

繼續閱讀