[mapguide-commits] r6228 - in trunk/Tools/Maestro: MaestroAPITests OSGeo.MapGuide.MaestroAPI OSGeo.MapGuide.MaestroAPI/Exceptions OSGeo.MapGuide.MaestroAPI/Mapping OSGeo.MapGuide.MaestroAPI/Properties

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Wed Nov 16 08:48:35 EST 2011


Author: jng
Date: 2011-11-16 05:48:35 -0800 (Wed, 16 Nov 2011)
New Revision: 6228

Added:
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Exceptions/DuplicateKeyException.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/Collections.cs
Modified:
   trunk/Tools/Maestro/MaestroAPITests/RuntimeMapTests.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/MapSelection.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/RuntimeMap.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.Designer.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.resx
Log:
#1852: Have runtime layers and groups as specialized collections. This provides the intuitive API that we had in Maestro 2.1. New unit tests have been added to verify that all add/insertion operations produce the correct draw orders. A whole load of existing RuntimeMap methods have been flagged with the Obsolete attribute and will be slated for removal with the next post-4.0 release.

Also, the RuntimeMap now has a CurrentConnection property, just like it did in 2.1

Modified: trunk/Tools/Maestro/MaestroAPITests/RuntimeMapTests.cs
===================================================================
--- trunk/Tools/Maestro/MaestroAPITests/RuntimeMapTests.cs	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/MaestroAPITests/RuntimeMapTests.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -38,6 +38,7 @@
     using OSGeo.MapGuide.MaestroAPI.CoordinateSystem;
     using System.Drawing;
     using OSGeo.MapGuide.ExtendedObjectModels;
+    using OSGeo.MapGuide.ObjectModels.LayerDefinition;
 
     [SetUpFixture]
     public class TestBootstrap
@@ -143,8 +144,8 @@
         private bool Matches(RuntimeMap map, IMapDefinition mdf)
         {
             if (map.MapDefinition != mdf.ResourceID) return false;
-            if (map.Groups.Length != mdf.GetGroupCount()) return false;
-            if (map.Layers.Length != mdf.GetLayerCount()) return false;
+            if (map.Groups.Count != mdf.GetGroupCount()) return false;
+            if (map.Layers.Count != mdf.GetLayerCount()) return false;
 
             foreach (var layer in map.Layers)
             {
@@ -614,6 +615,252 @@
             RenderLegendAndVerifyConvenience(map, 200, 600, "TestLegend12kConvenience_RailReAdded.png", "PNG");
         }
 
+        public virtual void TestMapManipulation2()
+        {
+            //This is mainly to exercise Insert() and to verify that draw orders come back as expected
+
+            var resSvc = _conn.ResourceService;
+            var mapSvc = _conn.GetService((int)ServiceType.Mapping) as IMappingService;
+            Assert.NotNull(mapSvc);
+
+            var mdf = resSvc.GetResource("Library://UnitTests/Maps/Sheboygan.MapDefinition") as IMapDefinition;
+            Assert.NotNull(mdf);
+
+            //FIXME: We have a problem. Can we calculate this value without MgCoordinateSystem and just using the WKT?
+            //The answer to this will answer whether we can actually support the Rendering Service API over http 
+            //using pure client-side runtime maps 
+            //
+            //The hard-coded value here was the output of MgCoordinateSystem.ConvertCoordinateSystemUnitsToMeters(1.0)
+            //for this particular map.
+            double metersPerUnit = 111319.490793274;
+            var cs = CoordinateSystemBase.Create(mdf.CoordinateSystem);
+            metersPerUnit = cs.MetersPerUnitX;
+            Trace.TraceInformation("Using MPU of: {0}", metersPerUnit);
+
+            //Empty the layer/group list because we will add them individually
+            var removeLayers = new List<IMapLayer>(mdf.MapLayer);
+            foreach (var removeMe in removeLayers)
+                mdf.RemoveLayer(removeMe);
+
+            var removeGroups = new List<IMapLayerGroup>(mdf.MapLayerGroup);
+            foreach (var removeMe in removeGroups)
+                mdf.RemoveGroup(removeMe);
+
+            //Now create our runtime map
+            var mid = "Session:" + _conn.SessionID + "//TestMapManipulation2.Map";
+            var map = mapSvc.CreateMap(mid, mdf, metersPerUnit);
+            map.ViewScale = 12000;
+            map.DisplayWidth = 1024;
+            map.DisplayHeight = 1024;
+            map.DisplayDpi = 96;
+
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(0, map.Groups.Count);
+
+            map.Groups.Add(new RuntimeMapGroup(map, "Group1"));
+            map.Groups.Add(new RuntimeMapGroup(map, "Group2"));
+            Assert.AreEqual(2, map.Groups.Count);
+
+            Assert.NotNull(map.Groups["Group1"]);
+            Assert.NotNull(map.Groups["Group2"]);
+            Assert.Null(map.Groups["Group3"]);
+
+            var layer = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/HydrographicPolygons.LayerDefinition"));
+            layer.Group = "Group1";
+
+            map.Layers.Insert(0, layer);
+            Assert.AreEqual(1, map.Layers.Count);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[0]);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+
+            var layer2 = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/Parcels.LayerDefinition"));
+            map.Layers.Insert(0, layer2);
+            layer2.Group = "Group1"; //Intentional
+
+            Assert.AreEqual(2, map.Layers.Count);
+            Assert.True(layer2 == map.Layers[0]);
+            Assert.False(layer2 == map.Layers[1]);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.NotNull(map.Layers["Parcels"]);
+            Assert.True(map.Layers["Parcels"] == map.Layers[0]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[1]);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer2.ObjectId));
+            //The important one
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[1].DisplayOrder);
+
+            var layer3 = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/Rail.LayerDefinition"));
+            layer3.Group = "Group2";
+            map.Layers.Insert(0, layer3);
+            Assert.AreEqual(3, map.Layers.Count);
+            Assert.True(layer3 == map.Layers[0]);
+            Assert.False(layer3 == map.Layers[1]);
+            Assert.False(layer3 == map.Layers[2]);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.NotNull(map.Layers["Parcels"]);
+            Assert.NotNull(map.Layers["Rail"]);
+            Assert.True(map.Layers["Rail"] == map.Layers[0]);
+            Assert.True(map.Layers["Parcels"] == map.Layers[1]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[2]);
+            //The important one
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[1].DisplayOrder);
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[2].DisplayOrder);
+            Assert.True(map.Layers[1].DisplayOrder < map.Layers[2].DisplayOrder);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            Assert.AreEqual(2, map.GetLayersOfGroup("Group1").Length);
+            Assert.AreEqual(1, map.GetLayersOfGroup("Group2").Length);
+
+            //Group1 has 2 layers
+            map.Groups.Remove("Group1");
+            Assert.AreEqual(1, map.Layers.Count);
+            Assert.Null(map.Groups["Group1"]);
+            Assert.True(map.Groups["Group2"] == map.Groups[0]);
+            Assert.Null(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            //Removing layer doesn't affect its group. It will still be there
+            map.Layers.Remove(layer3.Name);
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(1, map.Groups.Count);
+            Assert.NotNull(map.Groups["Group2"]);
+            Assert.Null(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            map.Groups.Remove("Group2");
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(0, map.Groups.Count);
+        }
+
+        public virtual void TestMapManipulation3()
+        {
+            //Pretty much the same as TestMapManipulation2() but with Add() instead of Insert()
+            //Add() puts the item at the end of the collection, so draw orders should reflect
+            //that
+
+            var resSvc = _conn.ResourceService;
+            var mapSvc = _conn.GetService((int)ServiceType.Mapping) as IMappingService;
+            Assert.NotNull(mapSvc);
+
+            var mdf = resSvc.GetResource("Library://UnitTests/Maps/Sheboygan.MapDefinition") as IMapDefinition;
+            Assert.NotNull(mdf);
+
+            //FIXME: We have a problem. Can we calculate this value without MgCoordinateSystem and just using the WKT?
+            //The answer to this will answer whether we can actually support the Rendering Service API over http 
+            //using pure client-side runtime maps 
+            //
+            //The hard-coded value here was the output of MgCoordinateSystem.ConvertCoordinateSystemUnitsToMeters(1.0)
+            //for this particular map.
+            double metersPerUnit = 111319.490793274;
+            var cs = CoordinateSystemBase.Create(mdf.CoordinateSystem);
+            metersPerUnit = cs.MetersPerUnitX;
+            Trace.TraceInformation("Using MPU of: {0}", metersPerUnit);
+
+            //Empty the layer/group list because we will add them individually
+            var removeLayers = new List<IMapLayer>(mdf.MapLayer);
+            foreach (var removeMe in removeLayers)
+                mdf.RemoveLayer(removeMe);
+
+            var removeGroups = new List<IMapLayerGroup>(mdf.MapLayerGroup);
+            foreach (var removeMe in removeGroups)
+                mdf.RemoveGroup(removeMe);
+
+            //Now create our runtime map
+            var mid = "Session:" + _conn.SessionID + "//TestMapManipulation2.Map";
+            var map = mapSvc.CreateMap(mid, mdf, metersPerUnit);
+            map.ViewScale = 12000;
+            map.DisplayWidth = 1024;
+            map.DisplayHeight = 1024;
+            map.DisplayDpi = 96;
+
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(0, map.Groups.Count);
+
+            map.Groups.Add(new RuntimeMapGroup(map, "Group1"));
+            map.Groups.Add(new RuntimeMapGroup(map, "Group2"));
+            Assert.AreEqual(2, map.Groups.Count);
+
+            Assert.NotNull(map.Groups["Group1"]);
+            Assert.NotNull(map.Groups["Group2"]);
+            Assert.Null(map.Groups["Group3"]);
+
+            var layer = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/HydrographicPolygons.LayerDefinition"));
+            layer.Group = "Group1";
+
+            map.Layers.Add(layer);
+            Assert.AreEqual(1, map.Layers.Count);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[0]);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+
+            var layer2 = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/Parcels.LayerDefinition"));
+            map.Layers.Add(layer2);
+            layer2.Group = "Group1"; //Intentional
+
+            Assert.AreEqual(2, map.Layers.Count);
+            Assert.False(layer2 == map.Layers[0]);
+            Assert.True(layer2 == map.Layers[1]);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.NotNull(map.Layers["Parcels"]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[0]);
+            Assert.True(map.Layers["Parcels"] == map.Layers[1]);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer2.ObjectId));
+            //The important one
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[1].DisplayOrder);
+
+            var layer3 = new RuntimeMapLayer(map, (ILayerDefinition)resSvc.GetResource("Library://UnitTests/Layers/Rail.LayerDefinition"));
+            layer3.Group = "Group2";
+            map.Layers.Add(layer3);
+            Assert.AreEqual(3, map.Layers.Count);
+            Assert.False(layer3 == map.Layers[0]);
+            Assert.False(layer3 == map.Layers[1]);
+            Assert.True(layer3 == map.Layers[2]);
+            Assert.NotNull(map.Layers["HydrographicPolygons"]);
+            Assert.NotNull(map.Layers["Parcels"]);
+            Assert.NotNull(map.Layers["Rail"]);
+            Assert.True(map.Layers["HydrographicPolygons"] == map.Layers[0]);
+            Assert.True(map.Layers["Parcels"] == map.Layers[1]);
+            Assert.True(map.Layers["Rail"] == map.Layers[2]);
+            //The important one
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[1].DisplayOrder);
+            Assert.True(map.Layers[0].DisplayOrder < map.Layers[2].DisplayOrder);
+            Assert.True(map.Layers[1].DisplayOrder < map.Layers[2].DisplayOrder);
+            Assert.NotNull(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            Assert.AreEqual(2, map.GetLayersOfGroup("Group1").Length);
+            Assert.AreEqual(1, map.GetLayersOfGroup("Group2").Length);
+
+            //Group1 has 2 layers
+            map.Groups.Remove("Group1");
+            Assert.AreEqual(1, map.Layers.Count);
+            Assert.Null(map.Groups["Group1"]);
+            Assert.True(map.Groups["Group2"] == map.Groups[0]);
+            Assert.Null(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.NotNull(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            //Removing layer doesn't affect its group. It will still be there
+            map.Layers.Remove(layer3.Name);
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(1, map.Groups.Count);
+            Assert.NotNull(map.Groups["Group2"]);
+            Assert.Null(map.Layers.GetByObjectId(layer.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer2.ObjectId));
+            Assert.Null(map.Layers.GetByObjectId(layer3.ObjectId));
+
+            map.Groups.Remove("Group2");
+            Assert.AreEqual(0, map.Layers.Count);
+            Assert.AreEqual(0, map.Groups.Count);
+        }
+
         public virtual void TestResourceEvents()
         {
             bool deleteCalled = false;
@@ -748,6 +995,18 @@
         }
 
         [Test]
+        public override void TestMapManipulation2()
+        {
+            base.TestMapManipulation2();
+        }
+
+        [Test]
+        public override void TestMapManipulation3()
+        {
+            base.TestMapManipulation3();
+        }
+
+        [Test]
         public override void TestLargeMapCreatePerformance()
         {
             base.TestLargeMapCreatePerformance();
@@ -802,6 +1061,18 @@
         }
 
         [Test]
+        public override void TestMapManipulation2()
+        {
+            base.TestMapManipulation2();
+        }
+
+        [Test]
+        public override void TestMapManipulation3()
+        {
+            base.TestMapManipulation3();
+        }
+
+        [Test]
         public override void TestLargeMapCreatePerformance()
         {
             base.TestLargeMapCreatePerformance();

Added: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Exceptions/DuplicateKeyException.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Exceptions/DuplicateKeyException.cs	                        (rev 0)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Exceptions/DuplicateKeyException.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -0,0 +1,71 @@
+#region Disclaimer / License
+// Copyright (C) 2011, Jackie Ng
+// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie at gmail.com
+// 
+// This library 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.1 of the License, or (at your option) any later version.
+// 
+// This library 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 this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+// 
+#endregion
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace OSGeo.MapGuide.MaestroAPI.Exceptions
+{
+    /// <summary>
+    /// Raised when an item with an pre-existing key is being added to a collection
+    /// </summary>
+    [global::System.Serializable]
+    public class DuplicateKeyException : Exception
+    {
+        //
+        // For guidelines regarding the creation of new exception types, see
+        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
+        // and
+        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
+        //
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DuplicateKeyException"/> class.
+        /// </summary>
+        public DuplicateKeyException() { }
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DuplicateKeyException"/> class.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        public DuplicateKeyException(string message) : base(message) { }
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DuplicateKeyException"/> class.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        /// <param name="inner">The inner.</param>
+        public DuplicateKeyException(string message, Exception inner) : base(message, inner) { }
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DuplicateKeyException"/> class.
+        /// </summary>
+        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
+        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
+        /// <exception cref="T:System.ArgumentNullException">
+        /// The <paramref name="info"/> parameter is null.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.Runtime.Serialization.SerializationException">
+        /// The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0).
+        ///   </exception>
+        protected DuplicateKeyException(
+          System.Runtime.Serialization.SerializationInfo info,
+          System.Runtime.Serialization.StreamingContext context)
+            : base(info, context) { }
+    }
+}

Added: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/Collections.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/Collections.cs	                        (rev 0)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/Collections.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -0,0 +1,557 @@
+#region Disclaimer / License
+// Copyright (C) 2011, Jackie Ng
+// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie at gmail.com
+// 
+// This library 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.1 of the License, or (at your option) any later version.
+// 
+// This library 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 this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+// 
+#endregion
+using System;
+using System.Collections.Generic;
+using System.Text;
+using OSGeo.MapGuide.MaestroAPI.Exceptions;
+
+namespace OSGeo.MapGuide.MaestroAPI.Mapping
+{
+    /// <summary>
+    /// A generic key/value collection
+    /// </summary>
+    /// <typeparam name="TKey">The type of the key.</typeparam>
+    /// <typeparam name="TVal">The type of the value.</typeparam>
+    public abstract class KeyValueCollection<TKey, TVal> : IList<TVal> where TVal : class
+    {
+        /// <summary>
+        /// The internal list of value
+        /// </summary>
+        protected List<TVal> _values;
+        /// <summary>
+        /// The internal dictionary of values keyed by its key
+        /// </summary>
+        protected Dictionary<TKey, TVal> _valuesByKey;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="KeyValueCollection&lt;TKey, TVal&gt;"/> class.
+        /// </summary>
+        protected KeyValueCollection()
+        {
+            _values = new List<TVal>();
+            _valuesByKey = new Dictionary<TKey, TVal>();
+        }
+
+        /// <summary>
+        /// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        /// </summary>
+        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
+        /// <returns>
+        /// The index of <paramref name="item"/> if found in the list; otherwise, -1.
+        /// </returns>
+        public int IndexOf(TVal item)
+        {
+            return _values.IndexOf(item);
+        }
+
+        /// <summary>
+        /// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public virtual void Insert(int index, TVal item)
+        {
+            var key = SelectKey(item);
+            if (_valuesByKey.ContainsKey(key))
+                throw new DuplicateKeyException(string.Format(Properties.Resources.DuplicateKeyExceptionMessage, key));
+
+            OnBeforeItemAdded(item);
+            _values.Insert(index, item);
+            _valuesByKey.Add(key, item);
+            OnItemAdded(item);
+        }
+
+        /// <summary>
+        /// Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.
+        /// </summary>
+        /// <param name="index">The zero-based index of the item to remove.</param>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public void RemoveAt(int index)
+        {
+            var item = this[index];
+            OnBeforeItemRemove(item);
+            _values.RemoveAt(index);
+
+            if (item != null)
+            {
+                var key = SelectKey(item);
+                _valuesByKey.Remove(key);
+
+                OnItemRemoved(item);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the element at the specified index.
+        /// </summary>
+        /// <returns>
+        /// The element at the specified index.
+        ///   </returns>
+        ///   
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public virtual TVal this[int index]
+        {
+            get
+            {
+                return _values[index];
+            }
+            set
+            {
+                if (_values[index] != null)
+                    RemoveAt(index);
+                _values[index] = value;
+            }
+        }
+
+        /// <summary>
+        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        /// </summary>
+        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+        ///   </exception>
+        public virtual void Add(TVal item)
+        {
+            var key = SelectKey(item);
+            if (_valuesByKey.ContainsKey(key))
+                throw new DuplicateKeyException(string.Format(Properties.Resources.DuplicateKeyExceptionMessage, key));
+
+            OnBeforeItemAdded(item);
+            _values.Add(item);
+            _valuesByKey.Add(key, item);
+            OnItemAdded(item);
+        }
+
+        /// <summary>
+        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        /// </summary>
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+        ///   </exception>
+        public virtual void Clear()
+        {
+            //We don't call Clear() directly because we need to propagate removal of each
+            //item back to the map
+            var items = new List<TVal>(this);
+            foreach (var item in items)
+            {
+                Remove(item);
+            }
+            //This shouldn't happen
+            if (_values.Count > 0)
+            {
+                System.Diagnostics.Trace.TraceWarning("Expected empty values collection!");
+                _values.Clear();
+            }
+            if (_valuesByKey.Count > 0)
+            {
+                System.Diagnostics.Trace.TraceWarning("Expected empty values collection!");
+                _valuesByKey.Clear();
+            }
+        }
+
+        /// <summary>
+        /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
+        /// </summary>
+        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+        /// <returns>
+        /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
+        /// </returns>
+        public bool Contains(TVal item)
+        {
+            return _values.Contains(item);
+        }
+
+        /// <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"/>.
+        /// -or-
+        /// Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.
+        ///   </exception>
+        public void CopyTo(TVal[] array, int arrayIndex)
+        {
+            _values.CopyTo(array, arrayIndex);
+        }
+
+        /// <summary>
+        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        /// </summary>
+        /// <returns>
+        /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        ///   </returns>
+        public int Count
+        {
+            get { return _values.Count; }
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+        /// </summary>
+        /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
+        ///   </returns>
+        public bool IsReadOnly
+        {
+            get { return false; }
+        }
+
+        /// <summary>
+        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        /// </summary>
+        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
+        /// <returns>
+        /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
+        /// </returns>
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
+        ///   </exception>
+        public bool Remove(TVal item)
+        {
+            OnBeforeItemRemove(item);
+            var ret = _values.Remove(item);
+            if (ret)
+            {
+                var key = SelectKey(item);
+                _valuesByKey.Remove(key);
+
+                OnItemRemoved(item);
+                return ret;
+            }
+            return ret;
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through the collection.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
+        /// </returns>
+        public IEnumerator<TVal> GetEnumerator()
+        {
+            return _values.GetEnumerator();
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>
+        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
+        /// </returns>
+        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+        {
+            return _values.GetEnumerator();
+        }
+
+        /// <summary>
+        /// Gets or sets the element at the specified index.
+        /// </summary>
+        /// <returns>
+        /// The element at the specified index.
+        ///   </returns>
+        ///   
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public TVal this[TKey key]
+        {
+            get { return _valuesByKey.ContainsKey(key) ? _valuesByKey[key] : null; }
+            set { _valuesByKey[key] = value; }
+        }
+
+        /// <summary>
+        /// Called before an item is added
+        /// </summary>
+        /// <param name="item">The item.</param>
+        protected virtual void OnBeforeItemAdded(TVal item) { }
+
+        /// <summary>
+        /// Called before an item is removed
+        /// </summary>
+        /// <param name="item">The item.</param>
+        protected virtual void OnBeforeItemRemove(TVal item) { }
+
+        /// <summary>
+        /// Called after an item has been added
+        /// </summary>
+        /// <param name="item">The item.</param>
+        protected abstract void OnItemAdded(TVal item);
+
+        /// <summary>
+        /// Called after an item has been removed. Note this is only called if the remove
+        /// operation removed the item in question
+        /// </summary>
+        /// <param name="value">The value.</param>
+        protected abstract void OnItemRemoved(TVal value);
+
+        /// <summary>
+        /// Selects the key given the value.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns></returns>
+        protected abstract TKey SelectKey(TVal value);
+    }
+
+    /// <summary>
+    /// A collection of runtime map layers
+    /// </summary>
+    public class RuntimeMapLayerCollection : KeyValueCollection<string, RuntimeMapLayer>
+    {
+        private RuntimeMap _parent;
+
+        private Dictionary<string, RuntimeMapLayer> _layerIdMap;
+
+        internal RuntimeMapLayerCollection(RuntimeMap parent)
+        {
+            _parent = parent;
+            _layerIdMap = new Dictionary<string, RuntimeMapLayer>();
+        }
+
+        /// <summary>
+        /// Adds the specified layer.
+        /// </summary>
+        /// <param name="layer">The layer.</param>
+        public override void Add(RuntimeMapLayer layer)
+        {
+            //calculate and set the zorder for the new layer
+            RuntimeMapLayer prevLayer = (this.Count == 0) ? null : this[this.Count - 1];
+            double zOrder = prevLayer == null ? RuntimeMap.Z_ORDER_TOP : prevLayer.DisplayOrder + RuntimeMap.Z_ORDER_INCREMENT;
+            layer.DisplayOrder = zOrder;
+
+            base.Add(layer);
+        }
+
+        /// <summary>
+        /// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public override void Insert(int index, RuntimeMapLayer item)
+        {
+            CalculateDisplayOrder(index, item);
+            base.Insert(index, item);
+        }
+
+        /// <summary>
+        /// Called when [before item added].
+        /// </summary>
+        /// <param name="layer">The layer.</param>
+        protected override void OnBeforeItemAdded(RuntimeMapLayer layer)
+        {
+            if (_layerIdMap.ContainsKey(layer.ObjectId))
+                throw new DuplicateKeyException(string.Format(Properties.Resources.DuplicateKeyExceptionMessage, layer.ObjectId));
+        }
+
+        /// <summary>
+        /// Called when [item added].
+        /// </summary>
+        /// <param name="layer">The layer.</param>
+        protected override void OnItemAdded(RuntimeMapLayer layer)
+        {
+            _layerIdMap[layer.ObjectId] = layer;
+            _parent.OnLayerAdded(layer);
+        }
+
+        /// <summary>
+        /// Called when [item removed].
+        /// </summary>
+        /// <param name="layer">The layer.</param>
+        protected override void OnItemRemoved(RuntimeMapLayer layer)
+        {
+            if (_layerIdMap.ContainsKey(layer.ObjectId))
+                _layerIdMap.Remove(layer.ObjectId);
+            _parent.OnLayerRemoved(layer);
+        }
+
+        /// <summary>
+        /// Selects the key given the value.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns></returns>
+        protected override string SelectKey(RuntimeMapLayer value)
+        {
+            return value.Name;
+        }
+
+        internal RuntimeMapLayer GetByObjectId(string id)
+        {
+            return _layerIdMap.ContainsKey(id) ? _layerIdMap[id] : null;
+        }
+
+        /// <summary>
+        /// Gets or sets the element at the specified index.
+        /// </summary>
+        /// <returns>
+        /// The element at the specified index.
+        ///   </returns>
+        ///   
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        ///   <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.
+        ///   </exception>
+        ///   
+        /// <exception cref="T:System.NotSupportedException">
+        /// The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
+        ///   </exception>
+        public override RuntimeMapLayer this[int index]
+        {
+            get
+            {
+                return base[index];
+            }
+            set
+            {
+                CalculateDisplayOrder(index, value);
+                base[index] = value;
+            }
+        }
+
+        /// <summary>
+        /// Removes the specified layer by its name.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        public void Remove(string name)
+        {
+            var layer = this[name];
+            if (layer != null)
+                Remove(layer);
+        }
+
+        private void CalculateDisplayOrder(int index, RuntimeMapLayer value)
+        {
+            //calculate zorder for the new layer
+            double zOrderLow, zOrderHigh;
+            RuntimeMapLayer layer;
+            if (index == 0)
+            {
+                zOrderLow = 0;
+                layer = base.Count > 0 ? base[index] : null;
+                if (layer != null)
+                    zOrderHigh = layer.DisplayOrder;
+                else
+                    zOrderHigh = 2.0 * RuntimeMap.Z_ORDER_INCREMENT;
+            }
+            else
+            {
+                layer = base[index - 1];
+                zOrderLow = layer.DisplayOrder;
+                layer = base.Count > index ? base[index] : null;
+                zOrderHigh = layer != null ? layer.DisplayOrder : zOrderLow + 2.0 * RuntimeMap.Z_ORDER_INCREMENT;
+            }
+            value.DisplayOrder = (zOrderLow + (zOrderHigh - zOrderLow) / 2.0);
+        }
+    }
+
+    /// <summary>
+    /// A collection of runtime map groups
+    /// </summary>
+    public class RuntimeMapGroupCollection : KeyValueCollection<string, RuntimeMapGroup>
+    {
+        private RuntimeMap _parent;
+
+        internal RuntimeMapGroupCollection(RuntimeMap parent)
+        {
+            _parent = parent;
+        }
+
+        /// <summary>
+        /// Called after an item has been added
+        /// </summary>
+        /// <param name="item">The item.</param>
+        protected override void OnItemAdded(RuntimeMapGroup item)
+        {
+            _parent.OnGroupAdded(item);
+        }
+
+        /// <summary>
+        /// Called after an item has been removed. Note this is only called if the remove
+        /// operation removed the item in question
+        /// </summary>
+        /// <param name="value">The value.</param>
+        protected override void OnItemRemoved(RuntimeMapGroup value)
+        {
+            _parent.OnGroupRemoved(value);
+        }
+
+        /// <summary>
+        /// Selects the key given the value.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns></returns>
+        protected override string SelectKey(RuntimeMapGroup value)
+        {
+            return value.Name;
+        }
+
+        /// <summary>
+        /// Removes the specified group by its name.
+        /// </summary>
+        /// <param name="groupName">Name of the group.</param>
+        public void Remove(string groupName)
+        {
+            var group = this[groupName];
+            if (group != null)
+                Remove(group);
+        }
+    }
+}

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/MapSelection.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/MapSelection.cs	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/MapSelection.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -120,7 +120,7 @@
                 if (n.Attributes["id"] != null)
                 {
                     string guid = n.Attributes["id"].Value;
-                    var l = _map.GetLayerByObjectId(guid);
+                    var l = _map.Layers.GetByObjectId(guid);
                     if (l != null)
                     {
                         foreach (System.Xml.XmlNode c in n.SelectNodes("Class"))

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/RuntimeMap.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/RuntimeMap.cs	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Mapping/RuntimeMap.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -30,6 +30,7 @@
 using OSGeo.MapGuide.ObjectModels.LayerDefinition;
 using System.Diagnostics;
 using OSGeo.MapGuide.MaestroAPI.Commands;
+using OSGeo.MapGuide.MaestroAPI.Exceptions;
 
 namespace OSGeo.MapGuide.MaestroAPI.Mapping
 {
@@ -120,28 +121,15 @@
     /// </example>
     public class RuntimeMap : MapObservable
     {
-        internal IFeatureService FeatureService { get; set; }
+        internal IFeatureService FeatureService { get { return this.CurrentConnection.FeatureService; } }
 
-        internal IResourceService ResourceService { get; set; }
+        internal IResourceService ResourceService { get { return this.CurrentConnection.ResourceService; } }
 
+        public IServerConnection CurrentConnection { get; private set; }
+
         internal Version SiteVersion { get; private set; }
 
         /// <summary>
-        /// The layer collection
-        /// </summary>
-        protected List<RuntimeMapLayer> _layers;
-        
-        /// <summary>
-        /// The group collection
-        /// </summary>
-        protected List<RuntimeMapGroup> _groups;
-
-        /// <summary>
-        /// The id to layer lookup dictionary
-        /// </summary>
-        protected Dictionary<string, RuntimeMapLayer> _layerIdMap;
-
-        /// <summary>
         /// The mapping service
         /// </summary>
         protected IMappingService _mapSvc;
@@ -169,8 +157,7 @@
             this.ObjectId = Guid.NewGuid().ToString();
             m_changeList = new Dictionary<string, ChangeList>();
             _finiteDisplayScales = new double[0];
-            this.ResourceService = conn.ResourceService;
-            this.FeatureService = conn.FeatureService;
+            this.CurrentConnection = conn;
             if (Array.IndexOf(conn.Capabilities.SupportedServices, (int)ServiceType.Mapping) >= 0)
             {
                 _mapSvc = (IMappingService)conn.GetService((int)ServiceType.Mapping);
@@ -179,9 +166,8 @@
             {
                 _getRes = (IGetResourceContents)conn.CreateCommand((int)CommandType.GetResourceContents);
             }
-            _layers = new List<RuntimeMapLayer>();
-            _groups = new List<RuntimeMapGroup>();
-            _layerIdMap = new Dictionary<string, RuntimeMapLayer>();
+            this.Layers = new RuntimeMapLayerCollection(this);
+            this.Groups = new RuntimeMapGroupCollection(this);
             this.Selection = new MapSelection(this);
         }
 
@@ -276,20 +262,18 @@
                 Trace.TraceInformation("[RuntimeMap.ctor]: {0} layers pre-cached", layerDefinitionCache.Count);
             }
 
-            int dispIndex = 0;
             //Load map layers
             foreach (var layer in mdf.MapLayer)
             {
                 var rtl = new RuntimeMapLayer(this, layer, GetLayerDefinition(layer.ResourceId));
-                rtl.DisplayOrder = (++dispIndex) * Z_ORDER_INCREMENT;
-                AddLayerInternal(rtl);
+                this.Layers.Add(rtl);
             }
 
             //Load map groups
             foreach (var group in mdf.MapLayerGroup)
             {
                 var grp = new RuntimeMapGroup(this, group);
-                _groups.Add(grp);
+                this.Groups.Add(grp);
             }
 
             //If base map specified load layers and groups there
@@ -304,12 +288,11 @@
                         {
                             var rtl = new RuntimeMapLayer(this, layer, GetLayerDefinition(layer.ResourceId)) { Visible = true };
                             rtl.Type = RuntimeMapLayer.kBaseMap;
-                            rtl.DisplayOrder = (++dispIndex) * Z_ORDER_INCREMENT;
-                            AddLayerInternal(rtl);
+                            this.Layers.Add(rtl);
                         }
                     }
                     var rtg = new RuntimeMapGroup(this, group);
-                    _groups.Add(rtg);
+                    this.Groups.Add(rtg);
                 }
 
                 //Init finite display scales
@@ -755,12 +738,12 @@
         /// <param name="s"></param>
         protected void SerializeLayerData(MgBinarySerializer s)
         {
-            s.Write((int)_groups.Count);
-            foreach (var g in _groups)
+            s.Write((int)this.Groups.Count);
+            foreach (var g in this.Groups)
                 g.Serialize(s);
 
-            s.Write(_layers.Count);
-            foreach (var t in _layers)
+            s.Write(this.Layers.Count);
+            foreach (var t in this.Layers)
                 t.Serialize(s);
         }
 
@@ -897,25 +880,24 @@
         {
             int groupCount = d.ReadInt32();
 
-            _groups.Clear();
+            this.Groups.Clear();
 
             for (int i = 0; i < groupCount; i++)
             {
                 RuntimeMapGroup g = new RuntimeMapGroup();
                 g.Deserialize(d);
-                _groups.Add(g);
+                this.Groups.Add(g);
             }
 
             int mapLayerCount = d.ReadInt32();
 
-            _layers.Clear();
-            _layerIdMap.Clear();
+            this.Layers.Clear();
 
             while (mapLayerCount-- > 0)
             {
                 RuntimeMapLayer t = new RuntimeMapLayer(this);
                 t.Deserialize(d);
-                AddLayerInternal(t);
+                this.Layers.Add(t);
             }
         }
 
@@ -934,15 +916,11 @@
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns></returns>
+        [Obsolete("Use the indexer of the Groups property instead")]
         public RuntimeMapGroup GetGroupByName(string name)
         {
             Check.NotNull(name, "name");
-            foreach (var group in _groups)
-            {
-                if (name.Equals(group.Name))
-                    return group;
-            }
-            return null;
+            return this.Groups[name];
         }
 
         /// <summary>
@@ -950,30 +928,28 @@
         /// </summary>
         /// <param name="id">The id.</param>
         /// <returns></returns>
+        [Obsolete("Use the Layers property instead")]
         public RuntimeMapLayer GetLayerByObjectId(string id)
         {
-            if (_layerIdMap.ContainsKey(id))
-                return _layerIdMap[id];
-
-            return null;
+            return this.Layers.GetByObjectId(id);
         }
 
         /// <summary>
-        /// Gets an array of all layers in this map
+        /// The collection of layers in this map
         /// </summary>
-        /// <value>The layers.</value>
-        public RuntimeMapLayer[] Layers
+        public RuntimeMapLayerCollection Layers
         {
-            get { return _layers.ToArray(); }
+            get;
+            private set;
         }
 
         /// <summary>
-        /// Gets an array of all groups in this map
+        /// The collection of groups in this map
         /// </summary>
-        /// <value>The groups.</value>
-        public RuntimeMapGroup[] Groups
+        public RuntimeMapGroupCollection Groups
         {
-            get { return _groups.ToArray(); }
+            get;
+            private set;
         }
 
         /// <summary>
@@ -986,106 +962,43 @@
         /// </summary>
         /// <param name="layer"></param>
         /// <returns></returns>
-        public void AddLayer(RuntimeMapLayer layer)
+        [Obsolete("Use the Layers property instead")]
+        internal void AddLayer(RuntimeMapLayer layer)
         {
-            if (_layerIdMap.ContainsKey(layer.ObjectId))
-                return;
-
-            AddLayerInternal(layer);
+            this.Layers.Add(layer);
         }
 
         /// <summary>
-        /// Adds the layer.
-        /// </summary>
-        /// <param name="layer">The layer.</param>
-        internal void AddLayerInternal(RuntimeMapLayer layer)
-        {
-            RuntimeMapLayer prevLayer = (_layers.Count == 0 ? null : _layers[_layers.Count - 1]);
-            double zOrder = prevLayer == null ? Z_ORDER_TOP : prevLayer.DisplayOrder + Z_ORDER_INCREMENT;
-            layer.DisplayOrder = zOrder;
-
-            _layers.Add(layer);
-            _layerIdMap[layer.ObjectId] = layer;
-
-            OnLayerAdded(layer);
-        }
-
-        /// <summary>
         /// Inserts the specified layer at the specified index. Does nothing
         /// if the layer instance is already in the map.
         /// </summary>
         /// <param name="index"></param>
         /// <param name="layer"></param>
+        [Obsolete("Use the Layers property instead")]
         public void InsertLayer(int index, RuntimeMapLayer layer)
         {
-            if (index >= _layers.Count || index < 0)
-                throw new ArgumentOutOfRangeException("index");
-
-            AddLayerInternal(layer, index);
+            this.Layers.Insert(index, layer);
         }
 
         /// <summary>
-        /// Adds the layer
-        /// </summary>
-        /// <param name="value"></param>
-        /// <param name="index"></param>
-        internal void AddLayerInternal(RuntimeMapLayer value, int index)
-        {
-            //calculate zorder for the new layer
-            double zOrderLow, zOrderHigh;
-            RuntimeMapLayer layer;
-            if(index == 0)
-            {
-                zOrderLow = 0;
-                layer = _layers.Count > 0 ? _layers[index] : null;
-                if (layer != null)
-                    zOrderHigh = layer.DisplayOrder;
-                else
-                    zOrderHigh = 2.0 * Z_ORDER_INCREMENT;
-            }
-            else
-            {
-                layer = _layers[index - 1];
-                zOrderLow = layer.DisplayOrder;
-                layer = _layers.Count > index ? _layers[index] : null;
-                zOrderHigh = layer != null ? layer.DisplayOrder : zOrderLow + 2.0 * Z_ORDER_INCREMENT;
-            }
-            value.DisplayOrder = (zOrderLow + (zOrderHigh - zOrderLow) / 2.0);
-
-            _layers.Insert(index, value);
-            _layerIdMap[value.ObjectId] = value;
-
-            OnLayerAdded(value);
-        }
-
-        /// <summary>
         /// Sets the layer to the specified index
         /// </summary>
         /// <param name="index">The index.</param>
         /// <param name="layer">The layer.</param>
+        [Obsolete("Use the Layers property instead")]
         public void SetLayerIndex(int index, RuntimeMapLayer layer)
         {
-            if (index >= _layers.Count || index < 0)
-                throw new ArgumentOutOfRangeException("index");
-
-            int idx = IndexOfLayer(layer);
-            if (idx >= 0)
-            {
-                RemoveLayerAt(idx);
-                AddLayerInternal(layer, index);
-            }
+            this.Layers[index] = layer;
         }
 
         /// <summary>
         /// Removes the layer at the specified index
         /// </summary>
         /// <param name="index">The index.</param>
+        [Obsolete("Use the Layers property instead")]
         public void RemoveLayerAt(int index)
         {
-            if (index >= _layers.Count || index < 0)
-                throw new ArgumentOutOfRangeException("index");
-
-            RemoveLayerInternal(index);
+            this.Layers.RemoveAt(index);
         }
 
         /// <summary>
@@ -1093,9 +1006,10 @@
         /// </summary>
         /// <param name="layer"></param>
         /// <returns></returns>
+        [Obsolete("Use the Layers property instead")]
         public int IndexOfLayer(RuntimeMapLayer layer)
         {
-            return _layers.IndexOf(layer);
+            return this.Layers.IndexOf(layer);
         }
 
         /// <summary>
@@ -1103,16 +1017,13 @@
         /// </summary>
         /// <param name="layerName"></param>
         /// <returns></returns>
+        [Obsolete("Use the Layers property instead")]
         public int IndexOfLayer(string layerName)
         {
             Check.NotEmpty(layerName, "layerName");
 
-            for (int i = 0; i < _layers.Count; i++)
-            {
-                if (layerName.Equals(_layers[i].Name))
-                    return i;
-            }
-            return -1;
+            var layer = this.Layers[layerName];
+            return this.Layers.IndexOf(layer);
         }
 
         /// <summary>
@@ -1154,68 +1065,35 @@
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns></returns>
-        public RuntimeMapGroup AddGroup(string name)
+        internal RuntimeMapGroup AddGroup(string name)
         {
             var group = new RuntimeMapGroup(this, name);
-            AddGroupInternal(group);
+            this.Groups.Add(group);
             return group;
         }
 
-        internal void AddGroupInternal(RuntimeMapGroup group)
-        {
-            _groups.Add(group);
-            OnGroupAdded(group);
-        }
-
         /// <summary>
-        /// Removes the layer
-        /// </summary>
-        /// <param name="layer">The layer.</param>
-        internal void RemoveLayerInternal(RuntimeMapLayer layer)
-        {
-            if (_layers.Remove(layer))
-            {
-                OnLayerRemoved(layer);
-            }
-        }
-
-        private void RemoveLayerInternal(int index)
-        {
-            if (index >= 0 && index < _layers.Count)
-            {
-                var layer = _layers[index];
-                _layers.RemoveAt(index);
-                OnLayerRemoved(layer);
-            }
-        }
-
-        /// <summary>
         /// Removes the specified layer.
         /// </summary>
         /// <param name="layer">The layer.</param>
+        [Obsolete("Use the Layers property instead")]
         public void RemoveLayer(RuntimeMapLayer layer)
         {
             Check.NotNull(layer, "layer");
-            RemoveLayerInternal(layer);
+            this.Layers.Remove(layer);
         }
 
         /// <summary>
         /// Removes the specified group.
         /// </summary>
         /// <param name="group">The group.</param>
+        [Obsolete("Use the Groups property instead")]
         public void RemoveGroup(RuntimeMapGroup group)
         {
             Check.NotNull(group, "group");
-            RemoveGroupInternal(group);
+            this.Groups.Remove(group);
         }
 
-        internal void RemoveGroupInternal(RuntimeMapGroup group)
-        {
-            if (_groups.Remove(group))
-            {
-                OnGroupRemoved(group);
-            }
-        }
 
         /// <summary>
         /// Gets the layers of the specified group
@@ -1226,7 +1104,7 @@
         {
             Check.NotEmpty(groupName, "groupName");
             List<RuntimeMapLayer> layers = new List<RuntimeMapLayer>();
-            foreach (var lyr in _layers)
+            foreach (var lyr in this.Layers)
             {
                 if (groupName.Equals(lyr.Group))
                     layers.Add(lyr);
@@ -1318,15 +1196,11 @@
         /// </summary>
         /// <param name="name">The name.</param>
         /// <returns></returns>
+        [Obsolete("Use the indexer of the Layer property instead")]
         public RuntimeMapLayer GetLayerByName(string name)
         {
             Check.NotEmpty(name, "name");
-            foreach (var layer in _layers)
-            {
-                if (name.Equals(layer.Name))
-                    return layer;
-            }
-            return null;
+            return this.Layers[name];
         }
 
         #region change tracking
@@ -1364,13 +1238,13 @@
         /// Called when a group is removed
         /// </summary>
         /// <param name="group"></param>
-        protected void OnGroupRemoved(RuntimeMapGroup group)
+        internal void OnGroupRemoved(RuntimeMapGroup group)
         {
             //???
             var layers = GetLayersOfGroup(group.Name);
             foreach (var lyr in layers)
             {
-                RemoveLayerInternal(lyr);
+                this.Layers.Remove(lyr);
             }
 
             TrackChange(group.ObjectId, false, Change.ChangeType.removed, string.Empty);
@@ -1380,7 +1254,7 @@
         /// Called when a group is added
         /// </summary>
         /// <param name="group"></param>
-        protected void OnGroupAdded(RuntimeMapGroup group)
+        internal void OnGroupAdded(RuntimeMapGroup group)
         {
             //???
 
@@ -1412,9 +1286,6 @@
         internal void OnLayerRemoved(RuntimeMapLayer layer)
         {
             //???
-            if (_layerIdMap.ContainsKey(layer.ObjectId))
-                _layerIdMap.Remove(layer.ObjectId);
-
             TrackChange(layer.ObjectId, true, Change.ChangeType.removed, string.Empty);
         }
 
@@ -1523,7 +1394,5 @@
         }
 
         #endregion
-
-        
     }
 }

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/OSGeo.MapGuide.MaestroAPI.csproj	2011-11-16 13:48:35 UTC (rev 6228)
@@ -193,6 +193,7 @@
     <Compile Include="CrossConnection\NsDoc.cs" />
     <Compile Include="CrossConnection\ResourceRebaser.cs" />
     <Compile Include="Exceptions\CustomPropertyNotFoundException.cs" />
+    <Compile Include="Exceptions\DuplicateKeyException.cs" />
     <Compile Include="Exceptions\ExpressionException.cs" />
     <Compile Include="Exceptions\MaestroException.cs" />
     <Compile Include="Exceptions\NestedExceptionMessageProcessor.cs" />
@@ -216,6 +217,7 @@
     <Compile Include="Feature\ReaderBase.cs" />
     <Compile Include="Feature\RecordBase.cs" />
     <Compile Include="IO\NsDoc.cs" />
+    <Compile Include="Mapping\Collections.cs" />
     <Compile Include="Mapping\NsDoc.cs" />
     <Compile Include="NsDoc.cs" />
     <Compile Include="ObjectModels\CommonTypes.cs" />

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.Designer.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.Designer.cs	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.Designer.cs	2011-11-16 13:48:35 UTC (rev 6228)
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // <auto-generated>
 //     This code was generated by a tool.
-//     Runtime Version:2.0.50727.4959
+//     Runtime Version:2.0.50727.5448
 //
 //     Changes to this file may cause incorrect behavior and will be lost if
 //     the code is regenerated.
@@ -314,6 +314,15 @@
         }
         
         /// <summary>
+        ///   Looks up a localized string similar to An item with the key value ({0}) already exists in the collection.
+        /// </summary>
+        internal static string DuplicateKeyExceptionMessage {
+            get {
+                return ResourceManager.GetString("DuplicateKeyExceptionMessage", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   Looks up a localized string similar to Bad Document: Expected attributes at the root level node.
         /// </summary>
         internal static string ERR_BAD_DOCUMENT_NO_ROOT_ATTRIBUTES {

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.resx
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.resx	2011-11-16 01:45:28 UTC (rev 6227)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Properties/Resources.resx	2011-11-16 13:48:35 UTC (rev 6228)
@@ -536,4 +536,7 @@
   <data name="DowngradedResource" xml:space="preserve">
     <value>Resource {0} downgraded to version {1}</value>
   </data>
+  <data name="DuplicateKeyExceptionMessage" xml:space="preserve">
+    <value>An item with the key value ({0}) already exists in the collection</value>
+  </data>
 </root>
\ No newline at end of file



More information about the mapguide-commits mailing list