转载请注明原文地址:https://www.cnblogs.com/litou/p/15035790.html
本文为《C#中使用GDAL3》的第二篇,总目录地址:https://www.cnblogs.com/litou/p/15004877.html
本文目录 |
一、介绍 |
二、读写数据内容 |
三、中文乱码问题 |
3.1、数据路径或数据文件名含中文时打开失败 |
3.2、读取中文字符串显示乱码 |
3.3、函数传入中文字符串参数报错 |
一、介绍
Shape文件是ESRI公司开发的一种空间数据开放格式,全称是ESRI Shapefile,该文件格式是由多个文件组成的,表示同一数据的一组文件的文件名必须相同。
要组成一份Shapefile,有三个文件是必不可少的,它们分别是shp、shx和dbf文件。组成如下:
必须文件 | .shp | 主文件,记录要素几何实体 |
.shx | 索引文件,记录每一个几何体在shp文件之中的位置 | |
.dbf | 数据文件,以dBase IV的数据表格式存储每个几何形状的属性数据 | |
可选文件 | .prj | 投影文件,保存地理坐标系统与投影信息 |
.sbx .sbn | 其他文件 |
二、读写数据内容
GDAL库内置支持读写ESRI Shapefile文件,无需其他插件支持。
示例Shapefile文件如下,存放在"C:\shp数据"下,图层名称为"测试面",类型为面,自定义字段有"Id"、"名称"和"大小",有两条记录。
以VS2015为例,修改自上一篇《C#中使用GDAL3(一):Windows下超详细编译C#版GDAL3.3.0(VS2015+.NET 4+32位/64位)》中第九部分"C#调用测试"的Demo程序。
由于Shapefile文件属于矢量数据,所以只需注册OGR驱动。
1、打开数据
调用Ogr.Open打开数据获取DataSource。这里有两种打开方法:
1)打开shp文件,即Ogr.Open的第一个参数是shp文件的路径,打开后得到的DataSource里面只含shp文件本身的一份数据。
2)打开shp文件所在目录,即Ogr.Open的第一个参数是shp文件所在目录的路径,打开后得到的DataSource里面包含该目录下所有shp文件数据。
另外,Open的第二个参数为打开方式,值0表示以只读方式打开,值1表示以读写方式打开。
2、获取图层对象和图层名称
调用DataSource.GetLayerByXXXXX获取图层对象,这里调用的是GetLayerByIndex,再调用Layer.GetName获取图层名称。
3、获取要素定义、字段定义和字段名称
调用Layer.GetLayerDefn获取要素定义,然后调用FeatureDefn.GetFieldDefn获取字段定义,再调用FieldDefn.GetName获取字段名称。
4、遍历要素记录
循环调用Layer.GetNextFeature获取每一条要素记录,直到获取的要素记录为null则循环结束。如需要重头开始遍历,需要调用Layer.ResetReading重置为开头位置。
5、读取要素字段值
调用Feature.GetFieldAsXXXXX获取要素字段值,这里调用的是GetFieldAsInteger、GetFieldAsString和GetFieldAsDouble的传入字段索引值的方法。
6、设置要素字段值
调用Feature.SetField写入要素字段值。
7、更新要素
调用Layer.SetFeature使要素修改生效。
using OSGeo.OGR;using System;namespace GdalDemo{ class Program { static void Main(string[] args) { Ogr.RegisterAll(); ReadShapeFile(); Console.ReadKey(); } static void ReadShapeFile() { //打开数据 string path = @"C:\shp数据"; DataSource ds = Ogr.Open(path, 1); //以可写方式打开 int lCount = ds.GetLayerCount(); for (int i = 0; i < lCount; i++) { //读取图层信息 Layer layer = ds.GetLayerByIndex(i); string layerName = layer.GetName(); Console.WriteLine(String.Format("图层名:{0}", layerName)); //读取字段信息 FeatureDefn featureDefn = layer.GetLayerDefn(); int fCount = featureDefn.GetFieldCount(); for (int j = 0; j < fCount; j++) { FieldDefn fieldDefn = featureDefn.GetFieldDefn(j); string fieldName = fieldDefn.GetName(); Console.WriteLine(String.Format("字段名:{0}", fieldName)); } //遍历要素 Feature feature; while ((feature = layer.GetNextFeature()) != null) { //读取要素信息 int id = feature.GetFieldAsInteger(0); Console.WriteLine(String.Format("字段值-id:{0}", id)); string name = feature.GetFieldAsString(1); Console.WriteLine(String.Format("字段值-名称:{0}", name)); double size = feature.GetFieldAsDouble(2); Console.WriteLine(String.Format("字段值-大小:{0}", size)); //设置要素信息 feature.SetField(0, id + 1); feature.SetField(1, name + "加"); feature.SetField(2, size + 10.12); //更新要素 layer.SetFeature(feature); //读取修改后要素信息 Console.WriteLine(String.Format("字段值-修改后-id:{0}", feature.GetFieldAsInteger(0))); Console.WriteLine(String.Format("字段值-修改后-名称:{0}", feature.GetFieldAsString(1))); Console.WriteLine(String.Format("字段值-修改后-大小:{0}", feature.GetFieldAsDouble(2))); //用字段名读取字段值 Console.WriteLine(String.Format("字段值-字段名值-id:{0}", feature.GetFieldAsInteger("id"))); try { Console.WriteLine(String.Format("字段值-字段名值-名称:{0}", feature.GetFieldAsString("名称"))); } catch { } } } } }}
运行结果如下:
1)数据读取正常
2)中文图层名称和字段名称均显示为乱码
3)读取字段值并显示中文内容正常
4)写入中文内容到字段正常
5)使用中文字段名获取字段值报错
三、中文乱码问题
要解决乱码问题,首先要理解为什么会出现乱码。根据GDAL的文档资料显示(https://gdal.org/development/rfc/rfc5_unicode.html),GDAL内部字符串使用UTF8编码,也就是说输入和输出的字符串均为UTF8编码,而我们使用的操作系统大部分都是简体中文版的Windows,其默认的字符串编码是GB2312(可通过C#下的System.Text.Encoding.Default.EncodingName得到),如果不做编码转换直接显示的话就会出现乱码问题。
3.1、数据路径或数据文件名含中文时打开失败
该情况在GDAL 3.3.0的C#接口中是不存在的。以Ogr库为例,在Ogr.cs中可以找到Open方法,其方法内通过Ogr.StringToUtf8Bytes函数处理,把传入的路径字符串转化为UTF8编码的字节数组,再传入内部的Open方法,所以在调用Ogr.Open方法时,无需对传入的路径字符串进行编码处理,也能正常使用。
另外在GDAL内部,参数GDAL_FILENAME_IS_UTF8的默认值是YES,所以无需显式重复设置为YES也能正常读取,设置为NO反而导致读取失败。
//Ogr.cspublic static DataSource Open(string utf8_path, int update){ IntPtr cPtr = OgrPINVOKE.Open(Ogr.StringToUtf8Bytes(utf8_path), update); DataSource ret = (cPtr == IntPtr.Zero) ? null : new DataSource(cPtr, true, ThisOwn_true()); if (OgrPINVOKE.SWIGPendingException.Pending) throw OgrPINVOKE.SWIGPendingException.Retrieve(); return ret;}internal static byte[] StringToUtf8Bytes(string str){ if (str == null) return null; int bytecount = System.Text.Encoding.UTF8.GetMaxByteCount(str.Length); byte[] bytes = new byte[bytecount + 1]; System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, bytes, 0); return bytes;}
3.2、读取中文字符串显示乱码
同样是读取字符串,读取中文图层名称和字段名称显示乱码,而读取中文字段值则正常。
//Layer.cspublic string GetName(){ string ret = OgrPINVOKE.Layer_GetName(swigCPtr); if (OgrPINVOKE.SWIGPendingException.Pending) throw OgrPINVOKE.SWIGPendingException.Retrieve(); return ret;}//FieldDefn.cspublic string GetName(){ string ret = OgrPINVOKE.FieldDefn_GetName(swigCPtr); if (OgrPINVOKE.SWIGPendingException.Pending) throw OgrPINVOKE.SWIGPendingException.Retrieve(); return ret;}//Feature.cspublic string GetFieldAsString(int id){ IntPtr cPtr = OgrPINVOKE.Feature_GetFieldAsString__SWIG_0(swigCPtr, id); string ret = Ogr.Utf8BytesToString(cPtr); if (OgrPINVOKE.SWIGPendingException.Pending) throw OgrPINVOKE.SWIGPendingException.Retrieve(); return ret;}//Ogr.csinternal unsafe static string Utf8BytesToString(IntPtr pNativeData){ if (pNativeData == IntPtr.Zero) return null; byte* pStringUtf8 = (byte*)pNativeData; int len = 0; while (pStringUtf8[len] != 0) len++; return System.Text.Encoding.UTF8.GetString(pStringUtf8, len);}
对比GetName和GetFieldAsString两个函数可以很明显看出来,GetFieldAsString通过调用Ogr.Utf8BytesToString将返回的UTF8编码的字节数组以UTF8方式解码为字符串,所以能够正常显示;而GetName则直接返回字符串(实际上编译器隐性调用了System.Text.Encoding.Default.GetString解码为字符串),由于没有使用UTF8解码导致显示为乱码。
不完美处理方法1:在C#中将乱码字符串还原为字节数组并重新以UTF8方式解码字符串。
具体方法为,将乱码的字符串先通过System.Text.Encoding.Default.GetBytes转换回乱码状态前的字节数组,再调用System.Text.Encoding.UTF8.GetString以UTF8的方式解码为系统识别的字符串。
该方法处理偶数个中文字符时可以正常还原,但处理奇数个中文字符时最后一个中文字符还原失败。测试代码如下:
using System;using System.Text;namespace Demo{ class Program { static void Main(string[] args) { string sOdd = "测试"; Console.WriteLine("原字符串:" + sOdd); string sOddUtf8 = Encoding.Default.GetString(Encoding.UTF8.GetBytes(sOdd)); Console.WriteLine("UTF8字符串:" + sOddUtf8); string sOddURestore = Encoding.UTF8.GetString(Encoding.Default.GetBytes(sOddUtf8)); Console.WriteLine("还原字符串:" + sOddURestore); Console.WriteLine(); string sEven = "测试面"; Console.WriteLine("原字符串:" + sEven); string sEvenUtf8 = Encoding.Default.GetString(Encoding.UTF8.GetBytes(sEven)); Console.WriteLine("UTF8字符串:" + sEvenUtf8); string sEvenURestore = Encoding.UTF8.GetString(Encoding.Default.GetBytes(sEvenUtf8)); Console.WriteLine("还原字符串:" + sEvenURestore); Console.ReadKey(); } }}
结果如下,"测试"可以正常还原,而"测试面"最后一个字还原失败。其原因是编码转换的问题,与平台无关,具体可参考该文章(https://blog.csdn.net/yuwenruli/article/details/6911401)。
要解决字符串乱码问题,只需要将原始UTF8编码的字节数组正确的使用UTF8解码即可。
前面提到GDAL中返回乱码字符串的函数(如GetName)已经把UTF8编码的字节数组返回为错误编码的字符串,且无法还原为完整的UTF8编码的字节数组,只能从源头开始处理。
解决方法2:在GDAL的C#源码中修正返回乱码字符串的函数。
以Layer.GetName为例,修改OgrPINVOKE.cs里面SWIGStringHelper的CreateString函数说明,并增加UTF8编码处理。(如没有找到.cs源码文件,执行一次nmake -f makefile.vc interface即可生成)
//OgrPINVOKE.cs//修改前protected class SWIGStringHelper{ public delegate string SWIGStringDelegate(string message); static SWIGStringDelegate stringDelegate = new SWIGStringDelegate(CreateString); [global::System.Runtime.InteropServices.DllImport("ogr_wrap", EntryPoint = "SWIGRegisterStringCallback_Ogr")] public extern static void SWIGRegisterStringCallback_Ogr(SWIGStringDelegate stringDelegate); static string CreateString(string cString) { return cString; } static SWIGStringHelper() { SWIGRegisterStringCallback_Ogr(stringDelegate); }}//修改后protected class SWIGStringHelper{ public delegate string SWIGStringDelegate(IntPtr ptr); //委托类型改为IntPtr static SWIGStringDelegate stringDelegate = new SWIGStringDelegate(CreateString); [global::System.Runtime.InteropServices.DllImport("ogr_wrap", EntryPoint = "SWIGRegisterStringCallback_Ogr")] public extern static void SWIGRegisterStringCallback_Ogr(SWIGStringDelegate stringDelegate); static string CreateString(IntPtr ptr) { return Ogr.Utf8BytesToString(ptr); //返回UTF8解码的字符串 } static SWIGStringHelper() { SWIGRegisterStringCallback_Ogr(stringDelegate); }}
修改完毕后,重新执行nmake -f makefile.vc和nmake -f makefile.vc install,将新生成的ogr_csharp.dll替换原来引入到C#项目中的文件并重......
原文转载:http://www.shaoqun.com/a/892244.html
跨境电商:https://www.ikjzd.com/
家得宝:https://www.ikjzd.com/w/1570
dojo:https://www.ikjzd.com/w/2052
黄劲:https://www.ikjzd.com/w/2426
转载请注明原文地址:https://www.cnblogs.com/litou/p/15035790.html 本文为《C#中使用GDAL3》的第二篇,总目录地址:https://www.cnblogs.com/litou/p/15004877.html本文目录一、介绍二、读写数据内容三、中文乱码问题3.1、数据路径或数据文件名含中文时打开失败3.2、读取中文字符串显示乱码3.3、函数传入中文字
四川秋季哪里景色最美?:http://www.30bags.com/a/429892.html
四川三星堆遗址"上新":这个宝藏省份还有多少惊喜我们不知道?:http://www.30bags.com/a/248800.html
四川赏桃花:出树香梢几树花 :http://www.30bags.com/a/414323.html
四川省2011年旅游会议今日召开 :http://www.30bags.com/a/409061.html
口述我和两女玩双飞的经历 那一夜夹得我好紧好爽:http://lady.shaoqun.com/m/a/248286.html
老师单独补课让我看她洗澡 老师叫我帮他解乳罩:http://lady.shaoqun.com/m/a/247002.html
解开胸罩揉着她的乳尖 我被6个男人玩到早上:http://www.30bags.com/m/a/249707.html
在体育器材室被学长玩 坐在学长腰上动高H:http://www.30bags.com/m/a/249743.html
招生在高校旁边开酒店的推广方式有争议:http://lady.shaoqun.com/a/428527.html
为什么大学附近有那么多小旅馆?大学生不都住校园吗?:http://lady.shaoqun.com/a/428528.html
大学周边的酒店往往供不应求。他们在里面做什么?你有过难忘的经历吗?:http://lady.shaoqun.com/a/428529.html
女人给你这四个暗示是因为想和你发生关系!:http://lady.shaoqun.com/a/428530.html
没有评论:
发表评论