在工作中,即使很不喜欢用shp文件,但还是经常会收到shp格式的文件。关于shp文件的吐糟就不多说了,除了文件小、字段名长度限制,不能储存弧线段等问题,还有一种处理方式也让人很是难受。
如上图,有些shp文件是分散在不同的文件夹里的,虽然方便了文件的分散传送,但没法像数据库一样统计管理,用起来很不方便。
这个工具的目的就是处理这一类型的文件,将分散的shp文件合并成一个数据库要素,并且保留文件名,转换成一个标记字段的字段值。
一、要实现的功能
如上图所示,点击【合并 shp文件】按钮,输入shp文件所在的文件夹和输出要保存的要素类的位置(上图中路径显示不全,要保存为数据库的要素类),点击【执行】即可。
生成的结果如下:
生成结果新增了2个字段,【SHP名称】是shp文件的文件名,【SHP路径】是shp文件的相对路径。
这样做是为了尽可能的保留shp文件的信息。尤其是【SHP路径】,可能包含了图斑所在的分区、类型等信息,将路径保留下来,方便后续更多信息的读取。
二、实现流程
1、复制shp文件夹下的所有内容
由于后续需要对shp原文件进行添加字段、计算字段等操作,为了避免对原文件的改动和破坏,这里首先复制了shp文件夹下的所有内容,作为后续处理的数据。
// 复制文件夹下的所有文件到新的位置public static void CopyAllFiles(string sourceDir, string destDir){//目标目录不存在则创建if (!Directory.Exists(destDir)){Directory.CreateDirectory(destDir);}DirectoryInfo sourceDireInfo = new DirectoryInfo(sourceDir);List<FileInfo> fileList = new List<FileInfo>();GetFileList(sourceDireInfo, fileList); // 获取源文件夹下的所有文件List<DirectoryInfo> dirList = new List<DirectoryInfo>();GetDirList(sourceDireInfo, dirList); // 获取源文件夹下的所有子文件夹// 创建目标文件夹结构foreach (DirectoryInfo dir in dirList){string sourcePath = dir.FullName;string destPath = sourcePath.Replace(sourceDir, destDir); // 替换源文件夹路径为目标文件夹路径if (!Directory.Exists(destPath)){Directory.CreateDirectory(destPath); // 创建目标文件夹}}// 复制文件到目标文件夹foreach (FileInfo fileInfo in fileList){string sourceFilePath = fileInfo.FullName;string destFilePath = sourceFilePath.Replace(sourceDir, destDir); // 替换源文件夹路径为目标文件夹路径File.Copy(sourceFilePath, destFilePath, true); // 复制文件,允许覆盖目标文件}}// 递归获取文件列表private static void GetFileList(DirectoryInfo dir, List<FileInfo> fileList){fileList.AddRange(dir.GetFiles()); // 添加当前文件夹下的所有文件foreach (DirectoryInfo directory in dir.GetDirectories()){GetFileList(directory, fileList); // 递归获取子文件夹下的文件}}// 递归获取子文件夹列表private static void GetDirList(DirectoryInfo dir, List<DirectoryInfo> dirList){dirList.AddRange(dir.GetDirectories()); // 添加当前文件夹下的所有子文件夹foreach (DirectoryInfo directory in dir.GetDirectories()){GetDirList(directory, dirList); // 递归获取子文件夹下的子文件夹}}
这里采用了递归的方式获取文件列表和子文件夹列表。按原文件夹的目录创建了新的文件夹,并且把文件按原位置一个一个复制过去。
这部分代码我也没仔细研究,网上现成的代码很多,抄了一段直接用了。
2、获取文件夹下的所有shp文件
1个shp要素其实包含了很多文件,但我们只需要后缀名为shp的文件即可。
按关键字获取特定文件的方法如下:
// 获取输入文件夹下的所有文件public static List<string> GetAllFiles(string folder_path, string key_word ="no match"){List<string> filePaths = new List<string>();// 获取当前文件夹下的所有文件string[] files = Directory.GetFiles(folder_path);// 判断是否包含关键字if (key_word == "no match"){filePaths.AddRange(files);}else{foreach (string file in files){// 检查文件名是否包含指定扩展名if (Path.GetExtension(file).Equals(key_word, StringComparison.OrdinalIgnoreCase)){filePaths.Add(file);}}}
方法引用:
// 获取所有shp文件
var files = ToolManager.GetAllFiles(shp_path, ".shp");
3、分别对单个shp文件单独新建标记字段,并赋值
// 获取路径标签string tab = folder_path.Substring(folder_path.LastIndexOf(@"\") + 1);// 分解文件夹目录,获取文件名和路径字段值foreach (var file in files){// 获取名称和路径string short_path = file.Replace(def_path + @"\", "");int index0 = short_path.IndexOf(@"\"); // 第一个【"\"】的位置int index1 = short_path.LastIndexOf(@"\"); // 最后一个【"\"】的位置int index2 = short_path.LastIndexOf(@".shp"); // 最后一个【".shp"】的位置string name = short_path.Substring(index1 + 1, index2 - index1 - 1);string path = tab + @"\" + short_path.Substring(index0 + 1, index1 - index0 - 1);// 添加2个标记字段Arcpy.AddField(file, @"SHP名称", "TEXT");Arcpy.AddField(file, @"SHP路径", "TEXT");Arcpy.CalculateField(file, @"SHP名称", "\"" + name + "\"");Arcpy.CalculateField(file, @"SHP路径", "\"" + path + "\"");}
这里主要是对字符串的切割,从绝对路径中获取要素名和相对路径。
不得不说,python在这方面比C#方便和灵活得多,有点怀念python。
获取所要的信息后,就可以将值赋给要素了。
PS:这里采用了GP工具来处理,其实挺不好,运行速度比较慢,如果碰上几百上千个要素,可能要等挺久。后面如果想到别的办法再来优化吧。(真的是边学边做,每过一个阶段回头看前面自己写的代码都像屎...)
最后将所有的shp要素合并即可:
// 合并要素
Arcpy.Merge(files, featureClass_path);
最后把复制出来的文件夹删掉:
// 删除复制的shp文件夹
Directory.Delete(shp_path, true);
以上就是工具的核心代码,完整代码请查看后面放出的工程文件。
需要注意的是为了不同工程的调用方便,一些通用方法我放到了几个类文件中。
三、工程文件分享
最后,放上工程文件的链接:
MergeSHPhttps://pan.baidu.com/s/1huEMRaDnxeVT3zHLonV6bw?pwd=2kgrPS:可以直接点击...bin\Debug\net6.0-windows\下的.esriAddinX文件直接安装。