// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk) // Copyright 2014 - Spartaco Giubbolini, Felix Obermaier // // This file is part of SharpMap. // SharpMap is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // SharpMap is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public License // along with SharpMap; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.Serialization; using GeoAPI; using GeoAPI.Geometries; using NetTopologySuite.IO; namespace SharpMap.Data { /// /// Represents an in-memory cache of spatial data. The FeatureDataSet is an extension of System.Data.DataSet /// /// Serialization is achieved using the approach described in http://support.microsoft.com/kb/829740/en-us /// [Serializable] public class FeatureDataSet : DataSet, ISerializable { /// /// Initializes a new instance of the FeatureDataSet class. /// public FeatureDataSet() {} /// /// Initializes a new instance of the FeatureDataSet class. /// /// serialization info /// streaming context protected FeatureDataSet(SerializationInfo info, StreamingContext context) { DataSetName = info.GetString("name"); Namespace = info.GetString("ns"); Prefix = info.GetString("prefix"); CaseSensitive = info.GetBoolean("case"); Locale = new CultureInfo(info.GetInt32("locale")); EnforceConstraints = info.GetBoolean("enforceCons"); var tables = (DataTable[]) info.GetValue("tables", typeof (DataTable[])); base.Tables.AddRange(tables); var constraints = (ArrayList)info.GetValue("constraints", typeof(ArrayList)); SetForeignKeyConstraints(constraints); var relations = (ArrayList)info.GetValue("relations", typeof(ArrayList)); SetRelations(relations); var extendedProperties = (PropertyCollection)info.GetValue("extendedProperties", typeof (PropertyCollection)); if (extendedProperties.Count > 0) // otherwise the next foreach throws exception... weird. foreach (DictionaryEntry keyPair in extendedProperties) ExtendedProperties.Add(keyPair.Key, keyPair.Value); } /// /// Gets the collection of tables contained in the FeatureDataSet /// public new FeatureTableCollection Tables { get { return new FeatureTableCollection(base.Tables); } } /// /// Copies the structure of the FeatureDataSet, including all FeatureDataTable schemas, relations, and constraints. Does not copy any data. /// /// public new FeatureDataSet Clone() { var cln = ((FeatureDataSet) (base.Clone())); return cln; } //private void InitClass() //{ // Prefix = ""; // Namespace = "sm"; // Locale = new CultureInfo("en-US"); // CaseSensitive = false; // EnforceConstraints = true; //} void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("name", DataSetName); info.AddValue("ns", Namespace); info.AddValue("prefix", Prefix); info.AddValue("case", CaseSensitive); info.AddValue("locale", Locale.LCID); info.AddValue("enforceCons", EnforceConstraints); info.AddValue("tables", base.Tables.OfType().ToArray()); info.AddValue("constraints", GetForeignKeyConstraints()); info.AddValue("relations", GetRelations()); info.AddValue("extendedProperties", ExtendedProperties); } private ArrayList GetForeignKeyConstraints() { var constraintList = new ArrayList(); var tables = base.Tables; for (int i = 0; i < tables.Count; i++) { DataTable dt = tables[i]; for (int j = 0; j < dt.Constraints.Count; j++) { Constraint c = dt.Constraints[j]; var fk = c as ForeignKeyConstraint; if (fk != null) { string constraintName = c.ConstraintName; var parentInfo = new int[fk.RelatedColumns.Length + 1]; parentInfo[0] = tables.IndexOf(fk.RelatedTable); for (int k = 1; k < parentInfo.Length; k++) { parentInfo[k] = fk.RelatedColumns[k - 1].Ordinal; } int[] childInfo = new int[fk.Columns.Length + 1]; childInfo[0] = i;//Since the constraint is on the current table, this is the child table. for (int k = 1; k < childInfo.Length; k++) { childInfo[k] = fk.Columns[k - 1].Ordinal; } var list = new ArrayList { constraintName, parentInfo, childInfo, new[] {(int) fk.AcceptRejectRule, (int) fk.UpdateRule, (int) fk.DeleteRule} }; var extendedProperties = new Hashtable(); if (fk.ExtendedProperties.Keys.Count > 0) { foreach (object propertyKey in fk.ExtendedProperties.Keys) { extendedProperties.Add(propertyKey, fk.ExtendedProperties[propertyKey]); } } list.Add(extendedProperties); constraintList.Add(list); } } } return constraintList; } private ArrayList GetRelations() { var relationList = new ArrayList(); var tables = base.Tables; foreach (DataRelation rel in Relations) { string relationName = rel.RelationName; var parentInfo = new int[rel.ParentColumns.Length + 1]; parentInfo[0] = tables.IndexOf(rel.ParentTable); for (int j = 1; j < parentInfo.Length; j++) { parentInfo[j] = rel.ParentColumns[j - 1].Ordinal; } var childInfo = new int[rel.ChildColumns.Length + 1]; childInfo[0] = tables.IndexOf(rel.ChildTable); for (int j = 1; j < childInfo.Length; j++) { childInfo[j] = rel.ChildColumns[j - 1].Ordinal; } var list = new ArrayList {relationName, parentInfo, childInfo, rel.Nested}; var extendedProperties = new Hashtable(); if (rel.ExtendedProperties.Keys.Count > 0) { foreach (object propertyKey in rel.ExtendedProperties.Keys) { extendedProperties.Add(propertyKey, rel.ExtendedProperties[propertyKey]); } } list.Add(extendedProperties); relationList.Add(list); } return relationList; } private void SetForeignKeyConstraints(ArrayList constraintList) { Debug.Assert(constraintList != null); var tables = base.Tables; foreach (ArrayList list in constraintList) { Debug.Assert(list.Count == 5); string constraintName = (string)list[0]; int[] parentInfo = (int[])list[1]; int[] childInfo = (int[])list[2]; int[] rules = (int[])list[3]; Hashtable extendedProperties = (Hashtable)list[4]; //ParentKey Columns. Debug.Assert(parentInfo.Length >= 1); DataColumn[] parentkeyColumns = new DataColumn[parentInfo.Length - 1]; for (int i = 0; i < parentkeyColumns.Length; i++) { Debug.Assert(tables.Count > parentInfo[0]); Debug.Assert(tables[parentInfo[0]].Columns.Count > parentInfo[i + 1]); parentkeyColumns[i] = tables[parentInfo[0]].Columns[parentInfo[i + 1]]; } //ChildKey Columns. Debug.Assert(childInfo.Length >= 1); DataColumn[] childkeyColumns = new DataColumn[childInfo.Length - 1]; for (int i = 0; i < childkeyColumns.Length; i++) { Debug.Assert(tables.Count > childInfo[0]); Debug.Assert(tables[childInfo[0]].Columns.Count > childInfo[i + 1]); childkeyColumns[i] = tables[childInfo[0]].Columns[childInfo[i + 1]]; } //Create the Constraint. ForeignKeyConstraint fk = new ForeignKeyConstraint(constraintName, parentkeyColumns, childkeyColumns); Debug.Assert(rules.Length == 3); fk.AcceptRejectRule = (AcceptRejectRule)rules[0]; fk.UpdateRule = (Rule)rules[1]; fk.DeleteRule = (Rule)rules[2]; //Extended Properties. Debug.Assert(extendedProperties != null); if (extendedProperties.Keys.Count > 0) { foreach (object propertyKey in extendedProperties.Keys) { fk.ExtendedProperties.Add(propertyKey, extendedProperties[propertyKey]); } } //Add the constraint to the child datatable. Debug.Assert(tables.Count > childInfo[0]); tables[childInfo[0]].Constraints.Add(fk); } } private void SetRelations(ArrayList relationList) { Debug.Assert(relationList != null); var tables = base.Tables; foreach (ArrayList list in relationList) { Debug.Assert(list.Count == 5); var relationName = (string)list[0]; var parentInfo = (int[])list[1]; var childInfo = (int[])list[2]; var isNested = (bool)list[3]; var extendedProperties = (Hashtable)list[4]; //ParentKey Columns. Debug.Assert(parentInfo.Length >= 1); var parentkeyColumns = new DataColumn[parentInfo.Length - 1]; for (int i = 0; i < parentkeyColumns.Length; i++) { Debug.Assert(tables.Count > parentInfo[0]); Debug.Assert(tables[parentInfo[0]].Columns.Count > parentInfo[i + 1]); parentkeyColumns[i] = tables[parentInfo[0]].Columns[parentInfo[i + 1]]; } //ChildKey Columns. Debug.Assert(childInfo.Length >= 1); var childkeyColumns = new DataColumn[childInfo.Length - 1]; for (int i = 0; i < childkeyColumns.Length; i++) { Debug.Assert(tables.Count > childInfo[0]); Debug.Assert(tables[childInfo[0]].Columns.Count > childInfo[i + 1]); childkeyColumns[i] = tables[childInfo[0]].Columns[childInfo[i + 1]]; } //Create the Relation, without any constraints[Assumption: The constraints are added earlier than the relations] var rel = new DataRelation(relationName, parentkeyColumns, childkeyColumns, false); rel.Nested = isNested; //Extended Properties. Debug.Assert(extendedProperties != null); if (extendedProperties.Keys.Count > 0) { foreach (object propertyKey in extendedProperties.Keys) { rel.ExtendedProperties.Add(propertyKey, extendedProperties[propertyKey]); } } //Add the relations to the dataset. Relations.Add(rel); } } } /// /// Represents the method that will handle the RowChanging, RowChanged, RowDeleting, and RowDeleted events of a FeatureDataTable. /// /// /// public delegate void FeatureDataRowChangeEventHandler(object sender, FeatureDataRowChangeEventArgs e); /// /// Represents one feature table of in-memory spatial data. /// [DebuggerStepThrough] [Serializable] public class FeatureDataTable : DataTable, IEnumerable { /// /// Initializes a new instance of the FeatureDataTable class with no arguments. /// public FeatureDataTable() { //InitClass(); } /// /// Creates an instance of this class from serialization /// /// The serialization info /// The streaming context public FeatureDataTable(SerializationInfo info, StreamingContext context) : base(info, context) { using (var ms = new MemoryStream((byte[]) info.GetValue("geometries", typeof(byte[])))) { using (var reader = new BinaryReader(ms)) { var wkbReader = new WKBReader(); while (reader.BaseStream.Position < reader.BaseStream.Length) { var rowIndex = reader.ReadInt32(); var row = (FeatureDataRow) Rows[rowIndex]; var srid = reader.ReadInt32(); var wkbSize = reader.ReadInt32(); var wkb = reader.ReadBytes(wkbSize); row.Geometry = wkbReader.Read(wkb); row.Geometry.SRID = srid; } } } } /// /// Initializes a new instance of the FeatureDataTable class with the specified table name. /// /// public FeatureDataTable(DataTable table) : base(table.TableName) { if (table.DataSet != null) { if ((table.CaseSensitive != table.DataSet.CaseSensitive)) { CaseSensitive = table.CaseSensitive; } if ((table.Locale.ToString() != table.DataSet.Locale.ToString())) { Locale = table.Locale; } if ((table.Namespace != table.DataSet.Namespace)) { Namespace = table.Namespace; } } Prefix = table.Prefix; MinimumCapacity = table.MinimumCapacity; DisplayExpression = table.DisplayExpression; } /// /// Gets the number of rows in the table /// [Browsable(false)] public int Count { get { return Rows.Count; } } /// /// Gets the feature data row at the specified index /// /// row index /// FeatureDataRow public FeatureDataRow this[int index] { get { return (FeatureDataRow) Rows[index]; } } #region IEnumerable Members /// /// Returns an enumerator for enumerating the rows of the FeatureDataTable /// /// public IEnumerator GetEnumerator() { return Rows.GetEnumerator(); } #endregion /// /// Occurs after a FeatureDataRow has been changed successfully. /// public event FeatureDataRowChangeEventHandler FeatureDataRowChanged; /// /// Occurs when a FeatureDataRow is changing. /// public event FeatureDataRowChangeEventHandler FeatureDataRowChanging; /// /// Occurs after a row in the table has been deleted. /// public event FeatureDataRowChangeEventHandler FeatureDataRowDeleted; /// /// Occurs before a row in the table is about to be deleted. /// public event FeatureDataRowChangeEventHandler FeatureDataRowDeleting; /// /// Adds a row to the FeatureDataTable /// /// public void AddRow(FeatureDataRow row) { Rows.Add(row); } /// /// Clones the structure of the FeatureDataTable, including all FeatureDataTable schemas and constraints. /// /// public new FeatureDataTable Clone() { var cln = ((FeatureDataTable) (base.Clone())); //cln.InitVars(); return cln; } /// /// /// /// protected override DataTable CreateInstance() { return new FeatureDataTable(); } //internal void InitVars() //{ // //this.columnFeatureGeometry = this.Columns["FeatureGeometry"]; //} //private void InitClass() //{ // //this.columnFeatureGeometry = new DataColumn("FeatureGeometry", typeof(GeoAPI.Geometries.IGeometry), null, System.Data.MappingType.Element); // //this.Columns.Add(this.columnFeatureGeometry); //} /// /// Creates a new FeatureDataRow with the same schema as the table. /// /// public new FeatureDataRow NewRow() { return (FeatureDataRow) base.NewRow(); } /// /// Creates a new FeatureDataRow with the same schema as the table, based on a datarow builder /// /// /// protected override DataRow NewRowFromBuilder(DataRowBuilder builder) { return new FeatureDataRow(builder); } /// /// /// /// protected override Type GetRowType() { return typeof (FeatureDataRow); } /// /// Raises the FeatureDataRowChanged event. /// /// protected override void OnRowChanged(DataRowChangeEventArgs e) { base.OnRowChanged(e); if ((FeatureDataRowChanged != null)) { FeatureDataRowChanged(this, new FeatureDataRowChangeEventArgs(((FeatureDataRow) (e.Row)), e.Action)); } } /// /// Raises the FeatureDataRowChanging event. /// /// protected override void OnRowChanging(DataRowChangeEventArgs e) { base.OnRowChanging(e); if ((FeatureDataRowChanging != null)) { FeatureDataRowChanging(this, new FeatureDataRowChangeEventArgs(((FeatureDataRow) (e.Row)), e.Action)); } } /// /// Raises the FeatureDataRowDeleted event /// /// protected override void OnRowDeleted(DataRowChangeEventArgs e) { base.OnRowDeleted(e); if ((FeatureDataRowDeleted != null)) { FeatureDataRowDeleted(this, new FeatureDataRowChangeEventArgs(((FeatureDataRow) (e.Row)), e.Action)); } } /// /// Raises the FeatureDataRowDeleting event. /// /// protected override void OnRowDeleting(DataRowChangeEventArgs e) { base.OnRowDeleting(e); if ((FeatureDataRowDeleting != null)) { FeatureDataRowDeleting(this, new FeatureDataRowChangeEventArgs(((FeatureDataRow) (e.Row)), e.Action)); } } ///// ///// Gets the collection of rows that belong to this table. ///// //public new DataRowCollection Rows //{ // get { throw (new NotSupportedException()); } // set { throw (new NotSupportedException()); } //} /// /// Removes the row from the table /// /// Row to remove public void RemoveRow(FeatureDataRow row) { Rows.Remove(row); } /// /// Populates a serialization information object with the data needed to serialize the . /// /// A object that holds the serialized data associated with the .A object that contains the source and destination of the serialized stream associated with the .The parameter is a null reference (Nothing in Visual Basic). public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); var rowIndex = 0; using (var ms = new MemoryStream()) { using (var writer = new BinaryWriter(ms)) { foreach (FeatureDataRow row in Rows) { if (row.IsFeatureGeometryNull()) continue; writer.Write(rowIndex++); writer.Write(row.Geometry.SRID); var wkb = row.Geometry.AsBinary(); writer.Write(wkb.Length); writer.Write(wkb); } ms.Seek(0, SeekOrigin.Begin); info.AddValue("geometries", ms.ToArray(), typeof(byte[])); } } } } /* /// /// Represents the collection of tables for the FeatureDataSet. /// [Serializable()] public class FeatureTableCollection : List { } */ /// /// Represents the collection of tables for the FeatureDataSet. /// It is a proxy to the object. /// It filters out those /// that are s. /// public class FeatureTableCollection : ICollection { private readonly DataTableCollection _dataTables; internal FeatureTableCollection(DataTableCollection dataTables) { _dataTables = dataTables; } /// public IEnumerator GetEnumerator() { var dataTables = new DataTable[_dataTables.Count]; _dataTables.CopyTo(dataTables, 0); foreach (var dataTable in dataTables) { if (dataTable is FeatureDataTable) yield return (FeatureDataTable) dataTable; } } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Method to add a to this set. /// /// If belongs to a different , /// this method attempts to remove it from that. If that is not possible, /// is copied () and the copy is then added. /// /// The feature data table to add public void Add(FeatureDataTable item) { var itemDataSet = item.DataSet; if (itemDataSet != null) { if (itemDataSet.Tables.CanRemove(item)) itemDataSet.Tables.Remove(item); else item = (FeatureDataTable) item.Copy(); } _dataTables.Add(item); } /// /// Method to add a range of s to the (Feature)DataTableCollection. /// /// The tables to add public void AddRange(IEnumerable items) { foreach (var item in items) { _dataTables.Add(item); } } /// /// Removes all items from the . /// /// The is read-only. public void Clear() { _dataTables.Clear(); } /// /// Determines whether the contains a specific value. /// /// /// true if is found in the ; otherwise, false. /// /// The object to locate in the . public bool Contains(FeatureDataTable item) { return _dataTables.Contains(item.TableName, item.Namespace); } /// /// Copies the elements of the to an , starting at a particular index. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.The zero-based index in at which copying begins. is null. is less than 0.The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(FeatureDataTable[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0) throw new ArgumentException("Negative arrayIndex"); var j = 0; for (var i = 0; i < _dataTables.Count; i++) { if (_dataTables[i] is FeatureDataTable) { if (j >= array.Length) throw new ArgumentException("Insufficient space provided for array"); array[j++] = (FeatureDataTable) _dataTables[i]; } } } /// /// Removes the first occurrence of a specific object from the . /// /// /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . /// /// The object to remove from the .The is read-only. public bool Remove(FeatureDataTable item) { if (_dataTables.CanRemove(item)) { _dataTables.Remove(item); return true; } return false; } /// /// Remove the feature data table at the provided index /// /// The index of the table to remove /// true if the table was successfully removed /// public bool RemoveAt(int index) { if (index < 0) throw new ArgumentOutOfRangeException("index"); var tmp = 0; DataTable tableToRemove = null; foreach (DataTable dataTable in _dataTables) { if (dataTable is FeatureDataTable) { if (tmp == index) { tableToRemove = dataTable; break; } tmp++; } } if (tableToRemove != null) return Remove((FeatureDataTable)tableToRemove); return false; } /// /// Gets the number of elements contained in the . /// /// /// The number of elements contained in the . /// public int Count { get { var i = 0; foreach (var dataTable in _dataTables) { if (dataTable is FeatureDataTable) i++; } return i; } } /// /// Gets a value indicating whether the is read-only. /// /// /// true if the is read-only; otherwise, false. /// public bool IsReadOnly { get { return false; } } /// /// An indexer to the feature data tables in this set /// /// The index of the feature data table to get /// The feature data table at index . /// Thrown, if the index is not in the valid range. public FeatureDataTable this[int index] { get { var i = 0; foreach (var dataTable in _dataTables) { if (dataTable is FeatureDataTable) { if (i == index) return (FeatureDataTable) dataTable; i++; } } throw new ArgumentOutOfRangeException("index"); } } } /// /// Represents a row of data in a FeatureDataTable. /// [DebuggerStepThrough] [Serializable] public class FeatureDataRow : DataRow { //private FeatureDataTable tableFeatureTable; private IGeometry _geometry; /// /// Creates an instance of this class /// /// The row builder public FeatureDataRow(DataRowBuilder rb) : base(rb) { } /// /// The geometry of the current feature /// public IGeometry Geometry { get { return _geometry; } set { if (_geometry == null) { _geometry = value; } else { if (ReferenceEquals(_geometry, value)) return; if (_geometry != null && _geometry.EqualsTopologically(value)) return; _geometry = value; if (RowState == DataRowState.Unchanged) SetModified(); } } } /// /// Returns true of the geometry is null /// /// public bool IsFeatureGeometryNull() { return _geometry == null; } /// /// Sets the geometry column to null /// public void SetFeatureGeometryNull() { _geometry = null; } } /// /// Occurs after a FeatureDataRow has been changed successfully. /// [DebuggerStepThrough] public class FeatureDataRowChangeEventArgs : EventArgs { private readonly DataRowAction _eventAction; private readonly FeatureDataRow _eventRow; /// /// Initializes a new instance of the FeatureDataRowChangeEventArgs class. /// /// /// public FeatureDataRowChangeEventArgs(FeatureDataRow row, DataRowAction action) { _eventRow = row; _eventAction = action; } /// /// Gets the row upon which an action has occurred. /// public FeatureDataRow Row { get { return _eventRow; } } /// /// Gets the action that has occurred on a FeatureDataRow. /// public DataRowAction Action { get { return _eventAction; } } } }