不通过ArcGIS写Personal Geodatabase(esri mdb)

Personal Geodatabse格式为mdb,是esri公司对Access做了扩展,由于既支持空间数据,也支持关系数据,在数据交换中,应用起来比较方便。由于PGeo并不没有对外公开的接口,想要直接操作起来并非易事,网上关于CAD格式数据写到shapefile,或者shapefile写到FileGDB的方式很多,也有很多著名的工具,如GDAL、NetTopologySuite等,GDAL只支持PGeo的读取,早期版本对中文支持也不好,很多时候涉及到空间数据交换时,大家都采用shapefile文件共享。通过分析PGeo格式mdb,分析存储空间要素的关键,采用硬写的方式实现shapefile数据写入mdb中,以ArcGIS 9.3格式mdb为例:

mdb中关于空间数据的关键表有:GDB_FeatureDataset(要素集名称及空间参考)、GDB_FeatureClasses(要素类)、GDB_FieldInfo(要素的字段定义)、GDB_GeomColumns(记录各个要素类存储几何信息的字段名称、几何类型、空间参考等)、GDB_ObjectClasses(存储要素类名称及所属的要素数据集ID)、GDB_SpatialRefs(空间参考),对于已有模板的,最重要的是GDB_FeatureDataset、GDB_GeomColumns、GDB_SpatialRefs,要设置三个表的空间参考(需要对应),设置GDB_GeomColumns中的Extent、GridSize和偏移量。另外就是所写图层的表以及对应的_shape_index表(图层空间要素的对应关系表)。

写记录到图层,从datatable中获取对应的要素属性

        /// <summary>
        /// 更新要素表数据(属性表以DataTable交换)
        /// </summary>
        /// <param name="dbfPath">dbf文件路径</param>
        /// <param name="tableName">mdb图层表名</param>
        /// <param name="propertyDataTable">属性表DataTable</param>
        /// <param name="propertyKey">属性表唯一字段名称(供比较使用)</param>
        /// <param name="dbfKey">dbf唯一字段名称(供比较用)</param>
        /// <returns></returns>
        public bool InsertFeatureClassWithProperty(string dbfPath, string tableName, DataTable propertyDataTable,string propertyKey,string dbfKey)
        {
            DbfFile odbf = new DbfFile(Encoding.UTF8);
            odbf.Open(dbfPath, FileMode.Open,FileAccess.Read,FileShare.ReadWrite);
            var orec = new DbfRecord(odbf.Header);
            int count = 0;
            for (int i = 0; i < odbf.Header.RecordCount; i++)
            {
                if (!odbf.Read(i, orec))
                    break;
                string condition = string.Format(@"{0}=""{1}""",propertyKey, orec[dbfKey]);
                OleDbDataAdapter adapter = mdbMgr.GetOleDbDataAdapter(tableName, condition);
                DataTable dataTable = new DataTable();
                adapter.Fill(dataTable);
                if (dataTable.Rows.Count == 0)
                {
                    foreach (DataRow dataRow in propertyDataTable.Rows)
                    {
                        if ((string)dataRow[propertyKey] == orec[dbfKey])
                        {
                            DataRow row = dataTable.NewRow();
                            row["Shape_Length"] = orec["SHAPE_LEN"].Trim();
                            row["Shape_Area"] = orec["SHAPE_AREA"].Trim();
                            row["SHAPE"] = shpFile.shpRecords[i].bytes;
                            for (int j = 0; j < propertyDataTable.Columns.Count; j++)
                                row[propertyDataTable.Columns[j].ColumnName] = dataRow[j];

                            dataTable.Rows.Add(row);
                            if (adapter.Update(dataTable) == 1)
                                count++;

                            string sqlString = string.Format("select ObjectID from {0} where {1} ", tableName,condition);
                            OleDbCommand cmd = new OleDbCommand(sqlString, mdbMgr.mConn);
                            int indexedObjectId = (int)cmd.ExecuteScalar();

                            //要素的最小外接矩形
                            double[] MBR = shpFile.shpRecords[i].MBR;
                            Tuple<double, double, double, double> featureExtent = new Tuple<double, double, double, double>(MBR[0], MBR[1], MBR[2], MBR[3]); //改成shprecord.mbr
                            InsertShapeIndex(tableName,indexedObjectId, featureExtent);

                            break;
                        }
                    }
                }
            }
            odbf.Close();
            return count == odbf.Header.RecordCount;
        }

一定要处理_shape_index表,否则arcgis打开后,属性表有记录,但无法定位和选中,那是因为空间关系不对造成的,gridsize可以设置为420,

        /// <summary>
        /// 写要素空间关联表
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="indexedObjectId"></param>
        /// <param name="extent"></param>
        public void InsertShapeIndex(string tableName, int indexedObjectId, Tuple<double, double, double, double> extent)
        {
            /*按分析,四个值计算公式应该是。但上述IdxOrginX和IdxOriginY都设置成0了。
                MinGX = (要素X最小值(MinX) - X方向偏移量(IdxOriginX)) / 格网尺寸(IdxGridSize);
                MinGY = (要素Y最小值(MinY) - Y方向偏移量(IdxOriginY)) / 格网尺寸(IdxGridSize);
                MaxGX = (要素X最大值(MaxX) - X方向偏移量(IdxOriginX)) / 格网尺寸(IdxGridSize);
                MaxGY = (要素Y最大值(MaxY) - Y方向偏移量(IdxOriginY)) / 格网尺寸(IdxGridSize);
            */

            //extent取整问题,实际上应该都是整数,
            //采用math.floor返回最大的小于或等于的整数,采用math.celling返回最小大于等于的整数,两者返回类型都是double
            double minGX = Math.Floor(extent.Item1 / gridSize);
            double minGY = Math.Floor(extent.Item2 / gridSize);
            double maxGX = Math.Ceiling(extent.Item3 / gridSize);
            double maxGY = Math.Ceiling(extent.Item4 / gridSize); ;
            tableName += "_SHAPE_Index";
            string sqlString = string.Format("insert into {0} (IndexedObjectId,MinGX,MinGY,MaxGX,MaxGY) " +
                "values({1},{2},{3},{4},{5})", tableName, indexedObjectId, minGX, minGY, maxGX, maxGY);
            OleDbCommand cmd = new OleDbCommand(sqlString, mdbMgr.mConn);
            cmd.ExecuteNonQuery();
        }

空间参考的更新

        public void UpdateGDB_GeomColumnsSRID(int srid,string tableName)
        {
            double[] MBR = shpFile.shpHeader.MBR;
            // 图形的最小外接矩形
            Tuple<double, double, double, double> layerExtent = new Tuple<double, double, double, double>(MBR[0], MBR[1], MBR[2], MBR[3]);
            string sqlString = string.Format("update GDB_GeomColumns set SRID ='{0}',ExtentLeft={1},ExtentBottom={2},ExtentRight={3},ExtentTop={4},IdxOriginX=0,IdxOriginY=0,IdxGridSize={5} where TableName='{6}' ", 
                srid,layerExtent.Item1, layerExtent.Item2, layerExtent.Item3, layerExtent.Item4,gridSize, tableName);
            OleDbCommand cmd = new OleDbCommand(sqlString, mdbMgr.mConn);
            cmd.ExecuteNonQuery();
        }

写mdb的关键:

  • 1、正确通过.shp文件获取图层的extent和每个要素的extent,该值是写GDB_GeomColumns和_Shape_Index的关键
  • 2、每插入一条要素,就要写一条对应的_shape_index记录,_shape_index的值要根据格网大小和偏移量计算,且注意Min和Max取值
  • 3、要素、要素集等空间参考要对应

关于shapefile文件的数据存储原理,请转https://baike.baidu.com/item/shapefile%E6%96%87%E4%BB%B6
关于shapefile各文件的解析请转https://github.com/YanzheZhang/SHPReaderCSharp

posted @ 2023-02-24 15:24  GIS民工  阅读(266)  评论(0编辑  收藏  举报