using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Text; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using GisSharpBlog.NetTopologySuite.IO.Handlers; namespace GisSharpBlog.NetTopologySuite.IO { /// /// This class is used to read and write ESRI Shapefiles. /// [Obsolete("Use ShapeFile as IFeatureProvider in SharpMap.Data")] public class Shapefile { internal const int ShapefileId = 9994; internal const int Version = 1000; /// /// Given a geomtery object, returns the equilivent shape file type. /// /// A Geometry object. /// The equilivent for the geometry object. public static ShapeGeometryType GetShapeType(IGeometry geom) { if (geom is IPoint) return ShapeGeometryType.Point; if (geom is IPolygon) return ShapeGeometryType.Polygon; if (geom is IMultiPolygon) return ShapeGeometryType.Polygon; if (geom is ILineString) return ShapeGeometryType.LineString; if (geom is IMultiLineString) return ShapeGeometryType.LineString; if (geom is IMultiPoint) return ShapeGeometryType.MultiPoint; return ShapeGeometryType.NullShape; } /// /// Returns the appropriate class to convert a shaperecord to an OGIS geometry given the type of shape. /// /// The shapefile type. /// An instance of the appropriate handler to convert the shape record to a Geometry object. public static ShapeHandler GetShapeHandler(ShapeGeometryType type) { switch (type) { case ShapeGeometryType.Point: case ShapeGeometryType.PointM: case ShapeGeometryType.PointZ: case ShapeGeometryType.PointZM: return new PointHandler(); case ShapeGeometryType.Polygon: case ShapeGeometryType.PolygonM: case ShapeGeometryType.PolygonZ: case ShapeGeometryType.PolygonZM: return new PolygonHandler(); case ShapeGeometryType.LineString: case ShapeGeometryType.LineStringM: case ShapeGeometryType.LineStringZ: case ShapeGeometryType.LineStringZM: return new MultiLineHandler(); case ShapeGeometryType.MultiPoint: case ShapeGeometryType.MultiPointM: case ShapeGeometryType.MultiPointZ: case ShapeGeometryType.MultiPointZM: return new MultiPointHandler(); default: return null; } } /// /// Returns an ShapefileDataReader representing the data in a shapefile. /// /// The filename (minus the . and extension) to read. /// The geometry factory to use when creating the objects. /// An ShapefileDataReader representing the data in the shape file. public static ShapefileDataReader CreateDataReader(string filename, GeometryFactory geometryFactory) { if (filename == null) throw new ArgumentNullException("filename"); if (geometryFactory == null) throw new ArgumentNullException("geometryFactory"); ShapefileDataReader shpDataReader= new ShapefileDataReader(filename,geometryFactory); return shpDataReader; } /// /// Creates a DataTable representing the information in a shape file. /// /// The filename (minus the . and extension) to read. /// The name to give to the table. /// The geometry factory to use when creating the objects. /// DataTable representing the data public static DataTable CreateDataTable(string filename, string tableName, GeometryFactory geometryFactory) { if (filename == null) throw new ArgumentNullException("filename"); if (tableName == null) throw new ArgumentNullException("tableName"); if (geometryFactory == null) throw new ArgumentNullException("geometryFactory"); ShapefileDataReader shpfileDataReader= new ShapefileDataReader(filename, geometryFactory); DataTable table = new DataTable(tableName); // use ICustomTypeDescriptor to get the properies/ fields. This way we can get the // length of the dbase char fields. Because the dbase char field is translated into a string // property, we lost the length of the field. We need to know the length of the // field when creating the table in the database. IEnumerator enumerator = shpfileDataReader.GetEnumerator(); bool moreRecords = enumerator.MoveNext(); ICustomTypeDescriptor typeDescriptor = (ICustomTypeDescriptor) enumerator.Current; foreach (PropertyDescriptor property in typeDescriptor.GetProperties()) { ColumnStructure column = (ColumnStructure) property; Type fieldType = column.PropertyType; DataColumn datacolumn = new DataColumn(column.Name, fieldType); if (fieldType== typeof(string)) // use MaxLength to pass the length of the field in the dbase file datacolumn.MaxLength=column.Length; table.Columns.Add( datacolumn ); } // add the rows - need a do-while loop because we read one row in order to determine the fields int iRecordCount = 0; object[] values = new object[shpfileDataReader.FieldCount]; do { iRecordCount++; shpfileDataReader.GetValues(values); table.Rows.Add(values); moreRecords = enumerator.MoveNext(); } while (moreRecords); return table; } /// /// Imports a shapefile into a database table. /// /// /// This method assumes a table has already been crated in the database. /// Calling this method does not close the connection that is passed in. /// /// /// /// /// public static int ImportShapefile(string filename, string connectionstring, string tableName) { using (SqlConnection connection = new SqlConnection(connectionstring)) { int rowsAdded = -1; PrecisionModel pm = new PrecisionModel(); GeometryFactory geometryFactory = new GeometryFactory(pm, -1); DataTable shpDataTable = CreateDataTable(filename, tableName, geometryFactory); string createTableSql = CreateDbTable(shpDataTable, true); SqlCommand createTableCommand = new SqlCommand(createTableSql, connection); connection.Open(); createTableCommand.ExecuteNonQuery(); string sqlSelect = String.Format("select * from {0}", tableName); SqlDataAdapter selectCommand = new SqlDataAdapter(sqlSelect, connection); // use a data adaptor - saves donig the inserts ourselves SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = new SqlCommand(sqlSelect, connection); SqlCommandBuilder custCB = new SqlCommandBuilder(dataAdapter); DataSet ds = new DataSet(); // fill dataset dataAdapter.Fill(ds, shpDataTable.TableName); // copy rows from our datatable to the empty table in the DataSet int i = 0; foreach (DataRow row in shpDataTable.Rows) { DataRow newRow = ds.Tables[0].NewRow(); newRow.ItemArray = row.ItemArray; //gotcha! - new row still needs to be added to the table. //NewRow() just creates a new row with the same schema as the table. It does //not add it to the table. ds.Tables[0].Rows.Add(newRow); i++; } // update all the rows in batch rowsAdded = dataAdapter.Update(ds, shpDataTable.TableName); int iRows = shpDataTable.Rows.Count; Debug.Assert(rowsAdded != iRows, String.Format("{0} of {1] rows were added to the database.", rowsAdded, shpDataTable.Rows.Count)); return rowsAdded; } } /// /// /// /// /// /// private static string CreateDbTable(DataTable table, bool deleteExisting) { StringBuilder sb = new StringBuilder(); if (deleteExisting) { sb.AppendFormat("if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[{0}]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n",table.TableName); sb.AppendFormat("drop table [dbo].[{0}]\n",table.TableName); } sb.AppendFormat("CREATE TABLE [dbo].[{0}] ( \n",table.TableName); for (int i=0; i < table.Columns.Count; i++) { string type = GetDbType(table.Columns[i].DataType, table.Columns[i].MaxLength ); string columnName = table.Columns[i].ColumnName; if (columnName=="PRIMARY") { columnName="DBF_PRIMARY"; Debug.Assert(false, "Shp2Db: Column PRIMARY renamed to PRIMARY."); Trace.WriteLine("Shp2Db: Column PRIMARY renamed to PRIMARY."); } sb.AppendFormat("[{0}] {1} ", columnName, type ); // the unique id column cannot be null if (i==1) sb.Append(" NOT NULL "); if (i+1 != table.Columns.Count) sb.Append(",\n"); } sb.Append(")\n"); // the DataSet update stuff requires a unique column - so give it the row colum that we added //sb.AppendFormat("ALTER TABLE [dbo].[{0}] WITH NOCHECK ADD CONSTRAINT [PK_{0}] PRIMARY KEY CLUSTERED ([{1}]) ON [PRIMARY]\n",table.TableName, table.Columns[1].ColumnName); return sb.ToString(); } /// /// /// /// /// /// private static string GetDbType(Type type, int length) { if (type == typeof(double)) return "real"; else if (type == typeof(float)) return "float"; else if (type == typeof(string)) return String.Format("nvarchar({0}) ", length); else if (type == typeof(byte[])) return "image"; else if (type == typeof(int)) return "int"; else if (type == typeof(char[])) return String.Format("nvarchar({0}) ", length); throw new NotSupportedException("Need to add the SQL type for "+type.Name); } } }