using System; using System.Collections.Generic; using System.Text; using OSGeo.MapGuide.MaestroAPI.Serialization; using System.Xml; using OSGeo.MapGuide.MaestroAPI.Feature; namespace OSGeo.MapGuide.MaestroAPI.Mapping { /// <summary> /// Represents a map selection /// </summary> /// <example> /// This sample shows how to list selected features in ASP.net. The sample expects 3 parameters: /// <list type="number"> /// <item><description>The session ID (SESSION)</description></item> /// <item><description>The runtime map name (MAPNAME)</description></item> /// <item><description>The selection XML (SELECTION)</description></item> /// </list> /// <code> /// <![CDATA[ /// string agent = "http://localhost/mapguide/mapagent/mapagent.fcgi"; /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection( /// "Maestro.Http", /// "Url", agent, /// "SessionId", Request.Params["SESSION"]); /// /// IMappingService mpSvc = (IMappingService)conn.GetService((int)ServiceType.Mapping); /// string rtMapId = "Session:" + conn.SessionID + "//" + Request.Params["MAPNAME"] + ".Map"; /// RuntimeMap rtMap = mpSvc.OpenMap(rtMapId); /// /// string xml = Request.Params["SELECTION"]; /// //The map selection contains one or more layer selections /// //each containing a one or more sets of identity property values /// //(because a feature may have multiple identity properties) /// /// MapSelection selection = new MapSelection(rtMap, System.Web.HttpUtility.UrlDecode(xml)); /// if (selection.Count > 0) /// { /// StringBuilder sb = new StringBuilder(); /// for (int i = 0; i < selection.Count; i++) /// { /// MapSelection.LayerSelection layerSel = selection[i]; /// sb.Append("<p>Layer: " + layerSel.Layer.Name + " (" + layerSel.Count + " selected item)"); /// sb.Append("<table>"); /// for (int j = 0; j < layerSel.Count; j++) /// { /// sb.Append("<tr>"); /// object[] values = layerSel[j]; /// for (int k = 0; k < values.Length; k++) /// { /// sb.Append("<td>"); /// sb.Append(values[k].ToString()); /// sb.Append("</td>"); /// } /// sb.AppendFormat("<td><a href='FeatureInfo.aspx?MAPNAME={0}&SESSION={1}&LAYERID={2}&ID={3}'>More Info</a></td>", /// rtMap.Name, /// conn.SessionID, /// layerSel.Layer.ObjectId, /// System.Web.HttpUtility.UrlEncode(layerSel.EncodeIDString(values))); /// sb.Append("</tr>"); /// } /// sb.Append("</table>"); /// Response.WriteLine("<p>Showing IDs of selected features</p>"); /// Response.WriteLine(sb.ToString()); /// } /// } /// else /// { /// Response.WriteLine("Nothing selected. Select some features first then run this sample again."); /// } /// ]]> /// </code> /// </example> public class MapSelection : IBinarySerializable, IList<MapSelection.LayerSelection> { private RuntimeMap _map; private List<LayerSelection> _layers; /// <summary> /// Constructor /// </summary> /// <param name="map"></param> public MapSelection(RuntimeMap map) { _map = map; _layers = new List<LayerSelection>(); } /// <summary> /// Constructor /// </summary> /// <param name="map"></param> /// <param name="xml"></param> public MapSelection(RuntimeMap map, string xml) : this(map) { LoadXml(xml); } /// <summary> /// Initialize this selection from the specified xml string /// </summary> /// <param name="xml"></param> public void LoadXml(string xml) { _layers.Clear(); System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); if (!string.IsNullOrEmpty(xml)) doc.LoadXml(xml); //There are two variations System.Xml.XmlNodeList lst = doc.SelectNodes("FeatureSet/Layer"); //NOXLATE if (lst.Count == 0) lst = doc.SelectNodes("FeatureInformation/FeatureSet/Layer"); //NOXLATE foreach (System.Xml.XmlNode n in lst) { if (n.Attributes["id"] != null) //NOXLATE { string guid = n.Attributes["id"].Value; //NOXLATE var l = _map.Layers.GetByObjectId(guid); if (l != null) { foreach (System.Xml.XmlNode c in n.SelectNodes("Class")) //NOXLATE { if (c.Attributes["id"] != null) //NOXLATE if (c.Attributes["id"].Value == l.QualifiedClassName) //NOXLATE _layers.Add(new LayerSelection(l, c.SelectNodes("ID"))); //NOXLATE } } } } } /// <summary> /// Returns an xml document that represents the current map selection /// </summary> /// <returns>An xml document that represents the current map selection</returns> public string ToXml() { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); System.Xml.XmlNode root = doc.AppendChild(doc.CreateElement("FeatureSet")); //NOXLATE foreach (LayerSelection layer in _layers) { System.Xml.XmlNode ln = root.AppendChild(doc.CreateElement("Layer")); //NOXLATE ln.Attributes.Append(doc.CreateAttribute("id")).Value = layer.Layer.ObjectId; //NOXLATE System.Xml.XmlNode cn = ln.AppendChild(doc.CreateElement("Class")); //NOXLATE cn.Attributes.Append(doc.CreateAttribute("id")).Value = layer.Layer.QualifiedClassName; //NOXLATE for (int i = 0; i < layer.Count; i++) cn.AppendChild(doc.CreateElement("ID")).InnerText = layer.EncodeIDString(layer[i]); //NOXLATE } return doc.OuterXml; } /// <summary> /// Represents a layer selection /// </summary> public class LayerSelection : IList<object[]> { private RuntimeMapLayer m_layer; private List<object[]> m_list = new List<object[]>(); /// <summary> /// Gets the layer that contains the selected objects /// </summary> public RuntimeMapLayer Layer { get { return m_layer; } } /// <summary> /// Internal helper to construct a LayerSelection /// </summary> /// <param name="layer">The layer that the selection belongs to</param> /// <param name="ids">A list of xml <ID> nodes</param> internal LayerSelection(RuntimeMapLayer layer, System.Xml.XmlNodeList ids) : this(layer) { foreach (System.Xml.XmlNode n in ids) Add(ParseIDString(n.InnerXml)); } /// <summary> /// Adds records from the specified reader into this selection /// </summary> /// <param name="reader">The reader</param> /// <param name="limit">The maximum number of records to add. Specify -1 for all</param> /// <returns>Number of records added</returns> public int AddFeatures(IReader reader, int limit) { int added = 0; if (limit < 0) { while (reader.ReadNext()) { AddFeature(reader); added++; } } else { while (reader.ReadNext() && added < limit) { AddFeature(reader); added++; } } reader.Close(); return added; } /// <summary> /// Adds the specified record to the selection /// </summary> /// <param name="record"></param> public void AddFeature(IRecord record) { var idProps = m_layer.IdentityProperties; object[] values = new object[idProps.Length]; for (int i = 0; i < idProps.Length; i++) { var prop = idProps[i]; //Don't null check because identity property values cannot be null values[i] = record[prop.Name]; } Add(values); } /// <summary> /// Encodes the given combined keyset into an ID string for use in the Xml /// </summary> /// <param name="values">The combined key</param> /// <returns>A base64 encoded ID string</returns> public string EncodeIDString(object[] values) { object[] tmp = NormalizeAndValidate(values); using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) { for (int i = 0; i < m_layer.IdentityProperties.Length; i++) { Type type = m_layer.IdentityProperties[i].Type; if (type == typeof(short)) { byte[] x = BitConverter.GetBytes((short)tmp[i]); ms.Write(x, 0, x.Length); } else if (type == typeof(int)) { byte[] x = BitConverter.GetBytes((int)tmp[i]); ms.Write(x, 0, x.Length); } else if (type == typeof(long)) { byte[] x = BitConverter.GetBytes((long)tmp[i]); ms.Write(x, 0, x.Length); } else if (type == typeof(double)) { byte[] x = BitConverter.GetBytes((double)tmp[i]); ms.Write(x, 0, x.Length); } else if (type == typeof(string)) { byte[] x = System.Text.Encoding.UTF8.GetBytes((string)tmp[i]); ms.Write(x, 0, x.Length); ms.WriteByte(0); } else throw new Exception(string.Format(Strings.ErrorUnsupportedPkType, type.ToString())); } return Convert.ToBase64String(ms.ToArray()); } } /// <summary> /// Parses a base64 encoded string with key values /// </summary> /// <param name="id">The base64 encoded ID string</param> /// <returns>The composite value key</returns> public object[] ParseIDString(string id) { return m_layer.ParseSelectionValues(id); } /// <summary> /// Constructs a new LayerSelection with a number of selected featured /// </summary> /// <param name="layer">The layer to represent</param> /// <param name="ids">The list of composite IDs that the layer supports</param> public LayerSelection(RuntimeMapLayer layer, IEnumerable<object[]> ids) : this(layer) { AddRange(ids); } /// <summary> /// Constructs a new LayerSelection with a number of selected featured /// </summary> /// <param name="layer">The layer to represent</param> public LayerSelection(RuntimeMapLayer layer) { if (layer == null) throw new ArgumentNullException("layer"); //NOXLATE if (layer.IdentityProperties.Length == 0 && layer.Parent.StrictSelection) throw new Exception(Strings.ErrorLayerHasNoPk); m_layer = layer; } /// <summary> /// Adds a composite key to the selection /// </summary> /// <param name="values">The composite key</param> public void Add(object[] values) { object[] tmp = NormalizeAndValidate(values); if (!this.Contains(tmp)) m_list.Add(tmp); } /// <summary> /// Ensures that the composite key types match the layers ID column types. /// The returned array is a copy of the one passed in /// </summary> /// <param name="values">The composite key</param> /// <returns>A copy of the composite key</returns> private object[] NormalizeAndValidate(object[] values) { if (values == null) throw new ArgumentNullException("values"); //NOXLATE if (values.Length != m_layer.IdentityProperties.Length) throw new Exception(string.Format(Strings.ErrorLayerKeyMismatch, m_layer.IdentityProperties.Length, values.Length)); object[] tmp = new object[values.Length]; for (int i = 0; i < values.Length; i++) { if (values[i] == null) throw new Exception(string.Format(Strings.ErrorNullKeyValue, m_layer.IdentityProperties[i].Name)); if (values[i].GetType() != m_layer.IdentityProperties[i].Type) try { tmp[i] = Convert.ChangeType(values[i], m_layer.IdentityProperties[i].Type); } catch (Exception ex) { throw new Exception(string.Format(Strings.ErrorFailedValueConversion, m_layer.IdentityProperties[i].Name, values[i].GetType(), m_layer.IdentityProperties[i].Type), ex); } else tmp[i] = values[i]; } return tmp; } /// <summary> /// Adds a number of composite keys /// </summary> /// <param name="lst">A list of composite keys</param> public void AddRange(IEnumerable<object[]> lst) { foreach (object[] x in lst) Add(x); } #region IList<object[]> Members /// <summary> /// Returns the index of the given composite key /// </summary> /// <param name="item">The composite key to look for</param> /// <returns>The index of the composite key or -1 if the key is not found</returns> public int IndexOf(object[] item) { object[] tmp = NormalizeAndValidate(item); for (int i = 0; i < m_list.Count; i++) { object[] tmpx = m_list[i]; bool matches = true; for (int j = 0; j < tmpx.Length; j++) if (tmpx[j] != item[j]) { matches = false; break; } if (matches) return i; } return -1; } /// <summary> /// Inserts a key at the specified location /// </summary> /// <param name="index">The index to insert the key at</param> /// <param name="item">The key to insert</param> public void Insert(int index, object[] item) { object[] tmp = NormalizeAndValidate(item); int ix = IndexOf(tmp); if (ix >= 0) RemoveAt(ix); Insert(index, item); } /// <summary> /// Removes the element at the specified location /// </summary> /// <param name="index">The index of the item to remove</param> public void RemoveAt(int index) { m_list.RemoveAt(index); } /// <summary> /// Gets or sets the composite key for the specified index /// </summary> /// <param name="index">The index for the composite key</param> /// <returns>The composite key</returns> public object[] this[int index] { get { object[] tmp = new object[m_layer.IdentityProperties.Length]; Array.Copy(m_list[index], tmp, tmp.Length); return tmp; } set { m_list[index] = NormalizeAndValidate(value); } } #endregion #region ICollection<object[]> Members /// <summary> /// Removes all composite keys from the collection /// </summary> public void Clear() { m_list.Clear(); } /// <summary> /// Returns a value indicating if the composite key is contained in the collection /// </summary> /// <param name="item">The composite key to look for</param> /// <returns>True if the collection contains the composite key, false otherwise</returns> public bool Contains(object[] item) { return IndexOf(item) >= 0; } /// <summary> /// Not implemented /// </summary> /// <param name="array"></param> /// <param name="arrayIndex"></param> public void CopyTo(object[][] array, int arrayIndex) { throw new NotImplementedException(); } /// <summary> /// Returns the number of composite keys (and thus selected objects) /// </summary> public int Count { get { return m_list.Count; } } /// <summary> /// Gets a value indicating if the collection is read-only /// </summary> public bool IsReadOnly { get { return false; } } /// <summary> /// Removes the given composite key from the collection /// </summary> /// <param name="item">The composite key to remove</param> /// <returns>True if the composite key was found and removed, false otherwise</returns> public bool Remove(object[] item) { int ix = IndexOf(item); if (ix < 0) return false; m_list.RemoveAt(ix); return true; } #endregion #region IEnumerable<object[]> Members /// <summary> /// Returns an enumerator for the collection /// </summary> /// <returns>An enumerator for the collection</returns> public IEnumerator<object[]> GetEnumerator() { return m_list.GetEnumerator(); } #endregion #region IEnumerable Members /// <summary> /// Returns an enumerator for the collection /// </summary> /// <returns>An enumerator for the collection</returns> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)m_list).GetEnumerator(); } #endregion } /// <summary> /// Serializes this instance using the specified serializer. /// </summary> /// <param name="s">The serializer.</param> public void Serialize(MgBinarySerializer s) { var m_selection = new XmlDocument(); m_selection.LoadXml(ToXml()); if (m_selection["FeatureSet"] == null) //NOXLATE { s.Write((int)0); return; } XmlNodeList lst = m_selection["FeatureSet"].SelectNodes("Layer"); //NOXLATE s.Write(lst.Count); foreach (XmlNode n in lst) { if (n.Attributes["id"] == null) //NOXLATE throw new Exception(Strings.ErrorSelectedLayerHasNoId); s.Write(n.Attributes["id"].Value); //NOXLATE XmlNodeList cls = n.SelectNodes("Class"); //NOXLATE s.Write(cls.Count); foreach (XmlNode c in cls) { s.Write(c.Attributes["id"].Value); //NOXLATE XmlNodeList ids = c.SelectNodes("ID"); //NOXLATE s.Write(ids.Count); foreach (XmlNode id in ids) s.Write(id.InnerText); } } } /// <summary> /// Deserializes this object using the specified deserializer. /// </summary> /// <param name="d">The deserializer.</param> public void Deserialize(MgBinaryDeserializer d) { XmlDocument doc = new XmlDocument(); XmlNode root = doc.AppendChild(doc.CreateElement("FeatureSet")); //NOXLATE int layerCount = d.ReadInt32(); for (int i = 0; i < layerCount; i++) { XmlNode layer = root.AppendChild(doc.CreateElement("Layer")); //NOXLATE layer.Attributes.Append(doc.CreateAttribute("id")).Value = d.ReadString(); //NOXLATE int classCount = d.ReadInt32(); for (int j = 0; j < classCount; j++) { XmlNode @class = layer.AppendChild(doc.CreateElement("Class")); //NOXLATE @class.Attributes.Append(doc.CreateAttribute("id")).Value = d.ReadString(); //NOXLATE int idCount = d.ReadInt32(); for (int k = 0; k < idCount; k++) @class.AppendChild(doc.CreateElement("ID")).InnerText = d.ReadString(); //NOXLATE } } LoadXml(doc.OuterXml); } #region IList<MapSelection.LayerSelection> Members /// <summary> /// Returns the index of the given layer /// </summary> /// <param name="item">The layer to look for</param> /// <returns>The index of the layer, or -1 if the layer is not in the collection</returns> public int IndexOf(MapSelection.LayerSelection item) { return IndexOf(item.Layer); } /// <summary> /// Returns the index of the given layer /// </summary> /// <param name="layer">The layer.</param> /// <returns> /// The index of the layer, or -1 if the layer is not in the collection /// </returns> public int IndexOf(RuntimeMapLayer layer) { for (int i = 0; i < _layers.Count; i++) if (_layers[i].Layer.ObjectId == layer.ObjectId) return i; return -1; } /// <summary> /// Inserts a selection layer into the collection /// </summary> /// <param name="index">The index to place the item at</param> /// <param name="item">The item to insert</param> public void Insert(int index, MapSelection.LayerSelection item) { _layers.Insert(index, item); } /// <summary> /// Inserts a selection layer into the collection /// </summary> /// <param name="index">The index to place the item at</param> /// <param name="layer">The layer.</param> public void Insert(int index, RuntimeMapLayer layer) { _layers.Insert(index, new LayerSelection(layer)); } /// <summary> /// Removes the item at the given index /// </summary> /// <param name="index">The index to remove the item at</param> public void RemoveAt(int index) { _layers.RemoveAt(index); } /// <summary> /// Gets or sets the selection layer at a given index /// </summary> /// <param name="index">The index to get or set the item for</param> /// <returns>The item at the given index</returns> public MapSelection.LayerSelection this[int index] { get { return _layers[index]; } set { if (value == null) throw new ArgumentNullException(); _layers[index] = value; } } /// <summary> /// Gets the selection layer at a given index /// </summary> /// <param name="index">The index to get or set the item for</param> /// <returns>The item at the given index</returns> public MapSelection.LayerSelection this[RuntimeMapLayer index] { get { return _layers[IndexOf(index)]; } } #endregion #region ICollection<MapSelection.LayerSelection> Members /// <summary> /// Adds the specified layer selection /// </summary> /// <param name="item"></param> public void Add(MapSelection.LayerSelection item) { if (item == null) throw new ArgumentNullException(); _layers.Add(item); } /// <summary> /// Adds the specified layer /// </summary> /// <param name="layer"></param> public void Add(RuntimeMapLayer layer) { if (!Contains(layer)) { var sel = new LayerSelection(layer); Add(sel); } } /// <summary> /// Clears this selction /// </summary> public void Clear() { _layers.Clear(); } /// <summary> /// Gets whether this selection contains the specified layer /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Contains(RuntimeMapLayer item) { return IndexOf(item) >= 0; } /// <summary> /// Gets whether this selection contains the specified layer selection /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Contains(MapSelection.LayerSelection item) { return IndexOf(item) >= 0; } /// <summary> /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index. /// </summary> /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param> /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param> /// <exception cref="T:System.ArgumentNullException"> /// <paramref name="array"/> is null. /// </exception> /// /// <exception cref="T:System.ArgumentOutOfRangeException"> /// <paramref name="arrayIndex"/> is less than 0. /// </exception> /// /// <exception cref="T:System.ArgumentException"> /// <paramref name="array"/> is multidimensional. /// -or- /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>. /// -or- /// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>. /// </exception> public void CopyTo(MapSelection.LayerSelection[] array, int arrayIndex) { throw new NotImplementedException(); } /// <summary> /// Gets the number of layers in this selection /// </summary> public int Count { get { return _layers.Count; } } /// <summary> /// Gets whether this is read only /// </summary> public bool IsReadOnly { get { return false; } } /// <summary> /// Removes the specified layer selection /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Remove(MapSelection.LayerSelection item) { int ix = IndexOf(item); if (ix < 0) return false; _layers.RemoveAt(ix); return true; } #endregion #region IEnumerable<LayerSelection> Members /// <summary> /// Gets the layer selection enumerator /// </summary> /// <returns></returns> public IEnumerator<MapSelection.LayerSelection> GetEnumerator() { return _layers.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)_layers).GetEnumerator(); } #endregion } }