目录
1. 正文
1.1. shp 文件本身的编码的问题
1.2. 设置读取的编码方式
1.2.1. GDAL 设置
1.2.2. 解码方式
1.2.3. 其他
2. 参考
1. 正文
最近在使用 GDAL 读写 Shp 格式中的属性字段的时候也遇到了中文乱码的问题, 总结下自己遇到的情况.
1.1. shp 文件本身的编码的问题
应该是由于 shp 格式加入了对宽字符的支持, 所以导致有段时间的 shp 文件和 ArcGIS 是存在不匹配的问题, 所以在网上搜索资源的时候遇到了大量的关于 ArcMap 显示 shp 属性表出现乱码的问题. 现在的 shp 格式的文件应该已经稳定下来了, 新添加了一个. cpg 的文件, 里面保存着属性表的编码格式:
图 1-1:shp 格式的. cpg 文件
从 ArcGIS10.2 开始, 只要是属性表编码与. cpg 文件记录的编码方式一致, 就不会再有显示乱码的问题. 网上查询到的修改注册表的方法, 我在 ArcGIS10.2 中试过, 似乎已经不再起效了.
那么对于没有. cpg 或者的情况, 应该可以看属性表. dbf 文件. 如果编码方式正确, 这个文件用文本编辑器打开是可以看到正常的中文的:
图 1-2:shp 格式的. dbf 文件
在正常显示中文情况下, 可以查看下文件的编码方式:
图 1-3: 查看编码方式
当然, 如果遇到乱码, 可以尝试用别的编码方式打开, 这样你就能知道属性表具体是什么编码了. 对于国内的情况来说, 只有 ANSI 编码和 UNICODE 编码两种: 其中简体中文系统中 ANSI 编码就是 GB2312 编码; UTF-8 是 UNICODE 编码的一种具体实现.
1.2. 设置读取的编码方式
1.2.1. GDAL 设置
可以通过全局设置函数 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","");
1.2.2. 解码方式
如果读取出来的字段属性仍然是乱码, 就应该考虑字符串的解码问题, 就是获取的字段属性字符串没有正确的解码出来. 例如读取 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;
- }
1.2.3. 其他
还有个值得注意的问题就是 Shp 格式的属性字段名称的长度最大只能支持 10 个字符. 如果采用 UTF-8 编码, 可能用不了几个中文字符就被截断了, 这个时候属性字段名称也可能存在乱码.
2. 参考
[1] GDAL/OGR 1.9.0 获取 shp 文件中中文字段值和属性值乱码文件解决
[2] UTF8 与 std:string 互转
来源: https://www.cnblogs.com/charlee44/p/11881894.html