前言:最近我们项目有个需求,就是将shp文件转为geojson。网上有很多的网站可以进行shp与geojson互转,但是这种做法并不能集成到我们系统中来,只适合单次调用。于是折腾了好多种办法,终于出来了,这里记录一下。

       由于我们的项目采用的架构是Postgresql+Geoserver+OpenLayers+Asp.Net Core MVC,所以我们最初的使用方法是将shp导入postgresql,这样我们就可以调用了。并且postgresql也提供了一个工具叫做,shp2pgsql,专门用来导入shp文件的,于是我们就试了一下。


        shp2pgsql的这个工具的使用cmd命令如下:


shp2pgsql -s 3857 -a -W "utf-8" -k -g "Shape"  D:\shp\test.shp "YZL"."YZL_ZLXB_PY" | psql -a test123  postgres


        由于网上有很多的文章介绍这个工具怎么使用,我们在这里就不详细介绍了。上面给出的cmd命令并没有输入密码,那是因为我们在postgresql的配置文件中已经写好了,这里同样不再介绍,只给出shp2pgsql工具的常用命令:


OPTIONS:


●-s


 设置srid,缺省为-1


●(-d|a|c|p)互斥选项:


     -d  重新建立表,并插入数据。


     -a  在同一个表中增加数据


     -c  建立新表,并插入数据。(缺省)



     -p  只创建表



●-g 指定要创建的表的空间字段名称(在追加数据时有用)


●-D  使用dump方式,缺省是生成sql



●-G  Use geography type (requires lon/lat data).



●-k  保持PostgreSQL标识符方式



●-i  使用int4类型dbf文件里的integer类型



●-I  在空间字段上建立索引



●-S  Generate simple geometries instead of MULTI geometries.



●-W shape文件属性列的字符格式。缺省是ascII



●-N 指定geometries为空时的操作(insert,skip,abort)



●-n  只导入dbf文件



●-?  显示帮助



        上面我们的命令指定的空间字段是“Shape”,shp2pgsql识别出来的sql语句是:



insert into test123."YZL"."YZL_ZLXB_PY"("字段1","字段2",Shape) values("value1","value2","ShapeValue")



         执行的时候总是报错,这就奇怪了,仔细看上面的sql语句,Shape是没有加引号的,所以一直报错,加上引号就好了。另外在网上查询资料的得到的结果是shp2pgsql是不识别大写的空间字段的,所以我加了一个测试的小写字段,居然真的成功了,这工具就是这么奇葩。。。。。



         由于我们的表已经建好了,是不允许随便乱加字段的,所以回过头来继续说我们的解决方案。所幸shp2pgsql这个工具是可以生成sql文件的,里面的内容就是一条条insert语句。它的命令是这样的:



shp2pgsql -s 3857 -a -W "utf-8" -k -g "Shape"  D:\shp\test.shp "YZL"."YZL_ZLXB_PY" >d:\test.sql



         这样,生成了test.sql文件,我们就可以用程序读取这个文件,把每一条sql语句都更改了,即给加上Shape加上引号。于是,将shp文件导入数据库的问题就成功解决了。



         至此,关于postgresql的相关操作就结束了



-----------------------------------------------------------------------------



          上面我们虽然成功的解决了将shp导入postgresql,但是我们只能导入特定的表,也就是说我们的shp文件表格式必须和我们数据库里的表结构一毛一样,这时非常操蛋的,于是我们就转向了鼎鼎大名的数据转换工具GDAL。



           于是,我们本着内事不决问百度,外事不决问谷歌的原则,打开了谷歌浏览器,一搜,果然在Stack Overflow上找到了解决方案,代码也不复杂,于是我们在Nuget服务器上找到了GDAL .Net Core的包,引用dll,开始上代码:



 C# Code 

  /// 
         /// 将shp转为geojson
         /// 
         /// shp文件全路径
         /// 
         public   static  List<</span>object> ConvertShp2Geojson(string shapeFilePath)
        {
            try
            {
                // 为了支持中文路径  
                Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
                // 为了使属性表字段支持中文  
                Gdal.SetConfigOption("SHAPE_ENCODING", "");
                Gdal.AllRegister();
                Ogr.RegisterAll();
                Driver drv = Ogr.GetDriverByName("ESRI Shapefile");
                var ds = drv.Open(shapeFilePath, 0);
                Layer layer = ds.GetLayerByIndex(0);
                Feature f;
                layer.ResetReading();
                List<</span>object> sb = new List<</span>object>();
                while ((f = layer.GetNextFeature()) != null)
                {
                    var geom = f.GetGeometryRef();
                    if (geom != null)
                    {
                        var geometryJson = geom.ExportToJson(new string[] { });
                        var obj = JsonConvert.DeserializeObject(geometryJson);
                        sb.Add(obj);
                    }
                }
                return sb;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        高高兴兴的发布网站,本地测试,能跑!!!来吧,部署远程服务器,测试,报错 The type initializer for 'OSGeo.OGR.OgrPINVOKE' threw an exception.


        有没有搞错!!!本地可以跑,为什么服务器不能跑,本地是win10系统,服务器是windows Server2008,难道环境不一样?于是我们就开始给服务器安装环境,GDAL是C++开发出来,我们装C++2005,不行!C++2008,还不行!!,C++2010,还不行!!!这就尴尬了,算了,环境问题太难搞,此路不通,我们就换思路。

        还是本着内事不决问百度,外事不决问谷歌的原则,我们打开了谷歌,一搜,原来GDAL也提供了一个工具,叫做ogr2ogr,就是做shp与geojson互转的。那么我们先来下这个工具,在GDAL的官网找了半天,没找到!!!这不应该啊。好吧,外面找不到,我们就回来找,一搜,果然有,居然要50个积分,你咋不去抢呢。还是忍痛弄了下来,打开压缩包一看,tmd,居然没有这个工具,你个死骗子。差评加曝光。


        不到黄河不死心,再次上GDAL官网,还是没找上,算了,下载一个它的包吧,也许在那里面,下载下来一搜,还真有!!!哈哈哈,皇天不负有心人呀。贴上下载方式:


        1.在GISinternal下载zip压缩包, 地址


离线svg转换pdf python svg转geojson_ogr2ogr



离线svg转换pdf python svg转geojson_转换_02


离线svg转换pdf python svg转geojson_geojson_03

        


       2.解压,找到bin\gdal\apps



离线svg转换pdf python svg转geojson_shp_04


 



       现在就可以开始转换了:


以管理员身份打开cmd窗口,进入这个路径:



        cd  D:\迅雷下载\release\bin\gdal\apps



1、shp转geojson命令:



        ogr2ogr -f "GeoJSON"  C:\goal.json  C:\source.shp



        其中goal.json文件会由这个工具自己创建



2、geojson转shp命令:



        ogr2ogr -f "ESRI Shapefile"  C:\goal.shp  C:\source.json 



        其中goal.shp文件也会由这个工具自己创建




知道了这些个命令,我们在程序里调用它就很容易了,不废话,直接上代码:



 C# Code 

/// 
/// 调用ogr2ogr.exe将shp转为geojson
/// 
/// shp文件路径
public   static   string  RunJsonConvertProcess( string  shpfile)
{
     try
    {
         string  dataDir = Directory.GetCurrentDirectory();
         string  accessPath = dataDir + @ "\bin\gdal\apps" ;
         string  fileName = Path.GetFileNameWithoutExtension(shpfile);
        var jsonPath = shpfile.Substring( 0 , shpfile.LastIndexOf( "\" ) +  1 ) + fileName +  ".json" ;
         if  (!File.Exists(accessPath +  "\\ogr2ogr.exe" ))
             throw   new  Exception( "不存在转换工具ogr2ogr" );
         string  cmd = $ "ogr2ogr -f " GeoJSON\ " {jsonPath} {shpfile}" ;
         string  str =  string .Format(@ "C:&cd {0}&{1}&{2}" , accessPath, cmd, "exit" );
        RunCmd(str);
         if (File.Exists(jsonPath))
             return  jsonPath;
         else
             return   "" ;
    }
     catch  (Exception e)
    {
         return   "error:" +e.Message;
    }

}
/// 
/// 执行cmd命令
/// 多命令请使用批处理命令连接符:
/// 
/// &:同时执行多个命令
/// |:将上一个命令的输出,作为下一个命令的输入
/// &&:当&&前的命令成功时,才执行&&后的命令
/// ||:当||前的命令失败时,才执行||后的命令
/// 
/// 
public   static   void  RunCmd( string  cmd)
{
    cmd = cmd.Trim().TrimEnd( '&' ) +  "&exit" ; //说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态
     using  (Process p =  new  Process())
    {
        Console.InputEncoding = Encoding.UTF8;
        p.StartInfo.FileName = @ "C:\Windows\System32\cmd.exe" ;
        p.StartInfo.UseShellExecute =  false ;         //是否使用操作系统shell启动
        p.StartInfo.RedirectStandardInput =  true ;    //接受来自调用程序的输入信息
        p.StartInfo.RedirectStandardOutput =  true ;   //由调用程序获取输出信息
        p.StartInfo.RedirectStandardError =  true ;    //重定向标准错误输出
        p.StartInfo.CreateNoWindow =  true ;           //不显示程序窗口
        p.Start(); //启动程序

         //向cmd窗口写入命令
        p.StandardInput.WriteLine(cmd);
        p.StandardInput.AutoFlush =  true ;

         //获取cmd窗口的输出信息
         //output = p.StandardOutput.ReadToEnd();
        p.WaitForExit(); //等待程序执行完退出进程
        p.Close();
    }
}

总结:shp2pgsql工具只能向某张特定的表导入数据,灵活性太差。ogr2ogr可以转换任意shp为geojson,非常灵活,我们当然选择后者。周五了,解决了一个大问题,可以愉快地过周末了,本次教程就分享到这里,我们下次再会。



------------------------------------------------------------------------------


2019.4.24更新:我在postgresql的安装目录中的bin文件夹中也找到ogr2ogr工具,太令人吃惊了!!!


离线svg转换pdf python svg转geojson_geojson_05


 



-----------------------------------------------------------------------------------------------------------------


2019.4.26更新:之前代码好好的运行,最近不知道怎么突然不能用了,报错'ogr2ogr ' is not recognized as an internal or external command, operable program or batch file.折腾了好久才发现是环境变量的问题,但是我们已经设置了环境变量了啊,但是程序不认,于是我们只好在程序里设置下环境变量。


废话不多说,我们直接上代码,这次的代码包括三个方法,因为之前的函数名命名并不好,所以我们这次重新命名一下:


1、Shp2GeoJsonByGDAL     使用GDAL将shp转为geojson


2、Shp2GeoJsonByOgr        调用ogr2ogr.exe将shp转为geojson


3、Shp2PostgreSql              调用shp2pgsql工具生成sql文件


4、RunCmd                          执行cmd命令



 C# Code 


  /// 
         /// 使用GDAL将shp转为geojson
         /// 
         /// shp文件全路径
         /// 
         public   static  List<</span>object> Shp2GeoJsonByGDAL(string shapeFilePath)
        {
            try
            {
                // 为了支持中文路径  
                Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
                // 为了使属性表字段支持中文  
                Gdal.SetConfigOption("SHAPE_ENCODING", "");
                Gdal.AllRegister();
                Ogr.RegisterAll();
                Driver drv = Ogr.GetDriverByName("ESRI Shapefile");
                var ds = drv.Open(shapeFilePath, 0);
                Layer layer = ds.GetLayerByIndex(0);
                Feature f;
                layer.ResetReading();
                List<</span>object> sb = new List<</span>object>();
                while ((f = layer.GetNextFeature()) != null)
                {
                    var geom = f.GetGeometryRef();
                    if (geom != null)
                    {
                        var geometryJson = geom.ExportToJson(new string[] { });
                        var obj = JsonConvert.DeserializeObject(geometryJson);
                        sb.Add(obj);
                    }
                }
                return sb;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        /// 
        /// 调用ogr2ogr.exe将shp转为geojson
        /// 
        /// shp文件路径
        public static string Shp2GeoJsonByOgr(string shpfile)
        {
            try
            {
                string dataDir = Directory.GetCurrentDirectory();
                string accessPath = dataDir + @"\bin\postgresql";
                string fileName = Path.GetFileNameWithoutExtension(shpfile);
                var jsonPath = shpfile.Substring(0, shpfile.LastIndexOf("\") + 1) + fileName + ".json";
                if (!File.Exists(accessPath + "\\ogr2ogr.exe"))
                    throw new Exception("不存在转换工具ogr2ogr");
                string cmd = $"ogr2ogr -f "GeoJSON\" {jsonPath} {shpfile}";
                string str = string.Format(@"C:&cd {0}&{1}&{2}", accessPath, cmd,"exit");

                RunCmd(accessPath,str);

                if(File.Exists(jsonPath))
                    return jsonPath;
                else
                    return "";
            }
            catch (Exception e)
            {
                return "error:"+e.Message;
            }

        }
        /// 
        /// 执行cmd命令
        /// 多命令请使用批处理命令连接符:
        /// 
        /// &:同时执行多个命令
        /// |:将上一个命令的输出,作为下一个命令的输入
        /// &&:当&&前的命令成功时,才执行&&后的命令
        /// ||:当||前的命令失败时,才执行||后的命令
        /// 
        /// 要设置的环境变量
        /// 要执行的cmd命令
        public static string RunCmd(string environmentVariable, string cmd)
        {
            cmd = cmd.Trim().TrimEnd('&') + "&exit";//说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态
            string res = "";
            using (Process p = new Process())
            {

                //先设置环境变量,再运行
                string path = Environment.GetEnvironmentVariable("path");
                Environment.SetEnvironmentVariable("path", path + environmentVariable + ";");

                //.NET Core不支持gbk和gb2312,这里需要处理一下
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                Console.InputEncoding = Encoding.UTF8;
                p.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
                p.StartInfo.UseShellExecute = false;        //是否使用操作系统shell启动
                p.StartInfo.RedirectStandardInput = true;   //接受来自调用程序的输入信息
                p.StartInfo.RedirectStandardOutput = true;  //由调用程序获取输出信息
                p.StartInfo.RedirectStandardError = true;   //重定向标准错误输出
                p.StartInfo.CreateNoWindow = true;          //不显示程序窗口
                p.Start();//启动程序

                //向cmd窗口写入命令
                p.StandardInput.WriteLine(cmd);
                p.StandardInput.AutoFlush = true;
                p.WaitForExit();//等待程序执行完退出进程

                //获取cmd窗口的输出信息
                string s = p.StandardOutput.ReadToEnd();
                //获取错误信息
                string error = p.StandardError.ReadToEnd();
                byte[] bytes = Encoding.GetEncoding("GB2312").GetBytes(s);
                res = Encoding.UTF8.GetString(bytes);
                
                p.Close();
            }
            return res;
        }
        /// 
        /// 调用shp2pgsql工具生成sql文件
        /// 
        /// 
        public static string Shp2PostgreSql(string shpfile)
        {
            try
            {
                string dataDir = Directory.GetCurrentDirectory();
                string accessPath = dataDir + @"\bin\postgresql";
                string fileName = Path.GetFileNameWithoutExtension(shpfile);
                var sqlPath = shpfile.Substring(0, shpfile.LastIndexOf("\") + 1) + fileName + ".sql";
                if (!File.Exists(accessPath + "\\shp2pgsql.exe"))
                    throw new Exception("不存在转换工具shp2pgsql");
                string cmd = $"shp2pgsql -s 3857 -a -W "gbk\" -k -g "Shape\"  {shpfile} "YZL\"."LDBG_QHTB_PY\" >{sqlPath}";
                string str = string.Format(@"C:&cd {0}&{1}&{2}", accessPath, cmd, "exit");
                RunCmd(accessPath, str);
                if (File.Exists(sqlPath))
                    return sqlPath;
                else
                    return "";
            }
            catch (Exception e)
            {
                return "error:" + e.Message;
            }
        }