#region Disclaimer / License // Copyright (C) 2010, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@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.Services; using OSGeo.MapGuide.ObjectModels.Common; using OSGeo.MapGuide.ObjectModels.MapDefinition; using OSGeo.MapGuide.ObjectModels; using OSGeo.MapGuide.MaestroAPI.Serialization; using OSGeo.MapGuide.MaestroAPI.Resource; using System.ComponentModel; using OSGeo.MapGuide.ObjectModels.LayerDefinition; using System.Diagnostics; using OSGeo.MapGuide.MaestroAPI.Commands; using OSGeo.MapGuide.MaestroAPI.Exceptions; namespace OSGeo.MapGuide.MaestroAPI.Mapping { //TODO: Verify the code examples here :) /// /// Represents a runtime instance of a Map Definition /// /// /// /// If you want to use this instance with the Rendering Service APIs, it is important to set the correct /// meters per unit value before calling the method, as an incorrect meters /// per unit value will produce incorrect images. /// /// /// Also note that to improve the creation performance, certain implementations of /// offer a helper to return a series of layer definitions in a batch (fetching /// layer definitions one at a time is the main performance bottleneck for large maps), batching can improve creation times by: /// /// /// HTTP: 2x /// Local: 3x (if using MapGuide 2.2 APIs. As this takes advantage of the GetResourceContents() API introduced in 2.2). For older versions of MapGuide there is no batching. /// /// /// In particular, the HTTP implementation of uses the /// class to fetch multiple layer definitions in parallel. If your code uses this implementation, be aware of this face and the performance implications /// involved, as an excessively large thread pool size may negatively affect stability of your MapGuide Server. /// /// /// /// How to create a with the correct meters per unit value using the MgCoordinateSystem API /// /// /// IServerConnection conn = ConnectionProviderRegistry.CreateConnection("Maestro.Http", /// "Username", "Administrator", /// "Password", "admin", /// "Url", "http://localhost/mapguide/mapagent/mapagent.fcgi"); /// /// //Create the Mapping Service. Some implementations of IServerConnection may not support this service, so /// //its best to inspect the capability object of this connection to determine if this service type is supported /// IMappingService mapSvc = (IMappingService)conn.GetService((int)ServiceType.Mapping); /// /// //Get our map definition /// ResourceIdentifier resId = new ResourceIdentifier("Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition"); /// IMapDefinition mdf = (IMapDefinition)conn.ResourceService.GetResource(resId.ToString()); /// /// //Calculate the meters per unit value, this requires the official MapGuide API. Otherwise, you need /// //to know this value up-front in order to render images with this instance /// double metersPerUnit = 1.0; /// if (!string.IsNullOrEmpty(mdf.CoordinateSystem)) /// { /// MgCoordinateSystemFactory factory = new MgCoordinateSystemFactory(); /// MgCoordinateSystem cs = factory.Create(mdf.CoordinateSystem); /// metersPerUnit = cs.ConvertCoordinateSystemUnitsToMeters(1.0); /// } /// /// //Generate our runtime map resource id. This must be session-based /// ResourceIdentifier rtMapId = new ResourceIdentifier(resId.Name, ResourceTypes.RuntimeMap, conn.SessionID); /// /// //Create the runtime map using our meters per unit value /// RuntimeMap map = mapSvc.CreateRuntimeMap(rtMapId, mdf, metersPerUnit); /// /// //Set some display parameters for this map /// map.ViewScale = 75000; /// map.DisplayWidth = 1024; /// map.DisplayHeight = 1024; /// map.DisplayDpi = 96; /// /// //We have to save it first before we can render from it or use any other API that requires this /// //current map state. Remember to call Save() everytime you change the state of the map /// map.Save(); /// /// //Now we can render a map /// using(Stream stream = mapSvc.RenderDynamicOverlay(map, null, "PNG")) /// { /// //Write this stream out to a file /// using (var fs = new FileStream("RenderMap.png", FileMode.OpenOrCreate)) /// { /// int read = 0; /// do /// { /// read = source.Read(buf, 0, buf.Length); /// target.Write(buf, 0, read); /// } while (read > 0); /// } /// } /// /// /// public class RuntimeMap : MapObservable { internal IFeatureService FeatureService { get { return this.CurrentConnection.FeatureService; } } internal IResourceService ResourceService { get { return this.CurrentConnection.ResourceService; } } public IServerConnection CurrentConnection { get; private set; } internal Version SiteVersion { get; private set; } /// /// The mapping service /// protected IMappingService _mapSvc; /// /// The GetResourceContents command /// protected IGetResourceContents _getRes; /// /// The amount to increment the Z order for successive layers being added /// public const double Z_ORDER_INCREMENT = 100.0; /// /// The draw order of the topmost layer /// public const double Z_ORDER_TOP = 100.0; internal RuntimeMap(IServerConnection conn) { _disableChangeTracking = true; this.WatermarkUsage = (int)WatermarkUsageType.Viewer; this.SiteVersion = conn.SiteVersion; this.SessionId = conn.SessionID; this.ObjectId = Guid.NewGuid().ToString(); m_changeList = new Dictionary(); _finiteDisplayScales = new double[0]; this.CurrentConnection = conn; if (Array.IndexOf(conn.Capabilities.SupportedServices, (int)ServiceType.Mapping) >= 0) { _mapSvc = (IMappingService)conn.GetService((int)ServiceType.Mapping); } if (Array.IndexOf(conn.Capabilities.SupportedCommands, (int)CommandType.GetResourceContents) >= 0) { _getRes = (IGetResourceContents)conn.CreateCommand((int)CommandType.GetResourceContents); } this.Layers = new RuntimeMapLayerCollection(this); this.Groups = new RuntimeMapGroupCollection(this); this.Selection = new MapSelection(this); } static IEnumerable GetLayerIds(IMapDefinition mdf) { foreach (var layer in mdf.MapLayer) { yield return layer.ResourceId; } if (mdf.BaseMap != null) { foreach (var group in mdf.BaseMap.BaseMapLayerGroup) { foreach (var layer in group.BaseMapLayer) { yield return layer.ResourceId; } } } } /// /// Sets the width, height and initial view scale /// /// /// public void InitialiseDisplayParameters(int width, int height) { this.DisplayWidth = width; this.DisplayHeight = height; var ext = this.MapExtent; var orgX1 = ext.MinX; var orgY2 = ext.MinY; var orgX2 = ext.MaxX; var orgY1 = ext.MaxY; if ((orgX1 - orgX2) == 0 || (orgY1 - orgY2) == 0) { orgX1 = -.1; orgX2 = .1; orgY1 = -.1; orgY2 = .1; } var scale = CalculateScale(Math.Abs(orgX2 - orgX1), Math.Abs(orgY2 - orgY1), this.DisplayWidth, this.DisplayHeight); this.ViewScale = scale; } private double CalculateScale(double mcsW, double mcsH, int devW, int devH) { var mpp = 0.0254 / this.DisplayDpi; if (devH * mcsW < devW * mcsH) return mcsW * this.MetersPerUnit / (devW * mpp); else return mcsH * this.MetersPerUnit / (devH * mpp); } /// /// Initializes a new instance of the class. /// /// The map definition to create this map from. /// The meters per unit value internal RuntimeMap(IMapDefinition mdf, double metersPerUnit) : this(mdf.CurrentConnection) { this.MetersPerUnit = metersPerUnit; this.MapDefinition = mdf.ResourceID; this.MapExtent = mdf.Extents.Clone(); this.DataExtent = mdf.Extents.Clone(); this.BackgroundColor = mdf.BackgroundColor; this.CoordinateSystem = mdf.CoordinateSystem; //TODO: infer real mpu from coordinate system //If a setup helper exists, use it to get required layers in a single //batch. Eliminating lots of chatter for really large maps. if (_getRes != null) { Trace.TraceInformation("[RuntimeMap.ctor]: Batching layer requests"); var res = _getRes.Execute(GetLayerIds(mdf)); //Pre-populate layer def cache so GetLayerDefinition() returns these //instead of making a new request foreach (var key in res.Keys) { var layer = res[key] as ILayerDefinition; if (layer != null) layerDefinitionCache.Add(key, layer); } Trace.TraceInformation("[RuntimeMap.ctor]: {0} layers pre-cached", layerDefinitionCache.Count); } //Load map layers foreach (var layer in mdf.MapLayer) { var rtl = _mapSvc.CreateMapLayer(this, layer); this.Layers.Add(rtl); } //Load map groups foreach (var group in mdf.MapLayerGroup) { var grp = _mapSvc.CreateMapGroup(this, group); this.Groups.Add(grp); } //If base map specified load layers and groups there if (mdf.BaseMap != null) { var bm = mdf.BaseMap; foreach (var group in bm.BaseMapLayerGroup) { if (group.HasLayers()) { foreach (var layer in group.BaseMapLayer) { var rtl = _mapSvc.CreateMapLayer(this, layer); rtl.Type = RuntimeMapLayer.kDynamic; //HACK: Setting Visible = true not allowed for kBaseMap rtl.Visible = true; rtl.Type = RuntimeMapLayer.kBaseMap; rtl.Group = group.Name; this.Layers.Add(rtl); } } var rtg = _mapSvc.CreateMapGroup(this, group); this.Groups.Add(rtg); } //Init finite display scales if (bm.ScaleCount > 0) { _finiteDisplayScales = new double[bm.ScaleCount]; for (int i = 0; i < bm.ScaleCount; i++) { _finiteDisplayScales[i] = bm.GetScaleAt(i); } } } this.LayerRefreshMode = 1; this.ViewScale = 1.0; //TODO: Calc from extents and other parameters this.DisplayDpi = 96; this.DisplayWidth = 0; this.DisplayHeight = 0; this.ViewCenter = this.DataExtent.Center(); _disableChangeTracking = false; } /// /// Gets or sets the map extents. /// /// The map extents. public IEnvelope MapExtent { get; private set; } /// /// The data extent /// protected IEnvelope _dataExtent; /// /// Gets or sets the data extent. /// /// The data extent. public IEnvelope DataExtent { get { return _dataExtent; } set { if (value == null) throw new ArgumentNullException(); if (_dataExtent == null) { _dataExtent = value; } else { _dataExtent.MaxX = value.MaxX; _dataExtent.MaxY = value.MaxY; _dataExtent.MinX = value.MinX; _dataExtent.MinY = value.MinY; } } } /// /// The dpi /// protected int _dpi; /// /// Gets or sets the display dpi. /// /// The display dpi. public int DisplayDpi { get { return _dpi; } set { SetField(ref _dpi, value, "DisplayDpi"); } } /// /// The display height /// protected int _dispHeight; /// /// Gets or sets the display height. /// /// The display height. public int DisplayHeight { get { return _dispHeight; } set { _dispHeight = value; } } /// /// The display width /// protected int _dispWidth; /// /// Gets or sets the display width. /// /// The display width. public int DisplayWidth { get { return _dispWidth; } set { _dispWidth = value; } } /// /// Gets or sets the map definition resource id /// /// The map definition resource id. public string MapDefinition { get; internal set; } /// /// Gets or sets the object id. /// /// The object id. public string ObjectId { get; internal set; } /// /// Gets or sets the session id. /// /// The session id. public string SessionId { get; internal set; } /// /// The view center /// protected IPoint2D _viewCenter; /// /// Gets or sets the view center. /// /// The view center. public IPoint2D ViewCenter { get { return _viewCenter; } set { _viewCenter = value; } } /// /// The view scale /// protected double _viewScale; /// /// Gets or sets the view scale. /// /// The view scale. public double ViewScale { get { return _viewScale; } set { SetField(ref _viewScale, value, "ViewScale"); } } /// /// The name of the map /// protected string _name; /// /// Gets or sets the name. /// /// The name. public string Name { get { return _name; } set { SetField(ref _name, value, "Name"); } } /// /// The Coordinate System WKT of the map /// protected string _mapSrs; /// /// Gets or sets the coordinate system in WKT format /// /// The coordinate system in WKT format. public string CoordinateSystem { get { return _mapSrs; } internal set { SetField(ref _mapSrs, value, "CoordinateSystem"); } } /// /// The background color of the map /// protected System.Drawing.Color _bgColor; /// /// Gets or sets the color of the background. /// /// The color of the background. public System.Drawing.Color BackgroundColor { get { return _bgColor; } set { SetField(ref _bgColor, value, "BackgroundColor"); } } private string _resId; /// /// Gets or sets the resource ID. When setting, if the name of this map has /// not been specified already, the name will be set based on this resource id /// /// The resource ID. public string ResourceID { get { return _resId; } set { SetField(ref _resId, value, "ResourceID"); if (this.Name == null) this.Name = ResourceIdentifier.GetName(_resId); } } /// /// Gets the type of the resource. /// /// The type of the resource. public ResourceTypes ResourceType { get { return ResourceTypes.RuntimeMap; } } /// /// Gets the meters per unit value. /// /// The meters per unit. public double MetersPerUnit { get; internal set; } /// /// Gets the watermark usage. Not applicable for version of MapGuide older than 2.3 /// public int WatermarkUsage { get; private set; } /// /// MapGuide internal value /// protected const int MgBinaryVersion = 262144; //1; /// /// MapGuide internal class id /// protected const int ClassId = 11500; //30500; /// /// Gets the layer refresh mode. /// /// The layer refresh mode. public int LayerRefreshMode { get; private set; } private double[] _finiteDisplayScales; /// /// Serializes this instance to the specified binary stream /// /// public void Serialize(MgBinarySerializer s) { if (s.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) { s.Write(MgBinaryVersion); s.WriteResourceIdentifier(this.ResourceID); } s.Write(this.Name); s.Write(this.ObjectId); s.WriteResourceIdentifier(this.MapDefinition); s.Write(this.CoordinateSystem); //base.m_extents.Serialize(s); SerializeExtent(this.MapExtent, s); s.WriteCoordinates(new double[] { this.ViewCenter.X, this.ViewCenter.Y }, 0); s.Write(this.ViewScale); SerializeExtent(this.DataExtent, s); s.Write(this.DisplayDpi); s.Write(this.DisplayWidth); s.Write(this.DisplayHeight); s.Write(Utility.SerializeHTMLColor(this.BackgroundColor, true)); s.Write(this.MetersPerUnit); if (s.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) s.Write(this.LayerRefreshMode); s.Write(_finiteDisplayScales.Length); foreach (double d in _finiteDisplayScales) s.Write(d); if (s.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) { SerializeChangeMap(s); if (s.SiteVersion >= new Version(2, 3)) //SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP2012)) { s.Write(this.WatermarkUsage); } s.Write((int)0); } else { SerializeLayerData(s); SerializeChangeMap(s); } } enum WatermarkUsageType { WMS = 1, Viewer = 2 } private static void SerializeExtent(IEnvelope env, MgBinarySerializer s) { if (s.SiteVersion <= SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1)) s.WriteClassId(18001); else s.WriteClassId(20001); s.Write((int)0); s.Write(env.MinX); s.Write(env.MinY); s.Write(env.MaxX); s.Write(env.MaxY); } private Dictionary m_changeList; internal class Change { public enum ChangeType { removed, added, visibilityChanged, displayInLegendChanged, legendLabelChanged, parentChanged, selectabilityChanged, definitionChanged }; public ChangeType Type { get; private set; } public string Params { get; private set; } public Change() { } public Change(ChangeType type, string param) { this.Type = type; this.Params = param; } } internal class ChangeList { public string ObjectId { get; private set; } public bool IsLayer { get; private set; } public List Changes { get; private set; } public ChangeList() { this.Changes = new List(); } public ChangeList(string objectId, bool isLayer) : this() { this.ObjectId = objectId; this.IsLayer = isLayer; } } private void SerializeChangeMap(MgBinarySerializer s) { s.Write(m_changeList.Count); foreach (ChangeList cl in m_changeList.Values) { s.Write(cl.IsLayer); s.Write(cl.ObjectId); s.Write(cl.Changes.Count); foreach (Change c in cl.Changes) { s.Write((int)c.Type); s.Write(c.Params); } } } /// /// Serializes the layer data to the specified binary stream /// /// protected void SerializeLayerData(MgBinarySerializer s) { s.Write((int)this.Groups.Count); //Workaround a deserialization quirk in the MgMap. It deserializes groups sequentially //without first checking if a parented group exists before doing the parent association // //We workaround this by re-ordering the groups, so that unparented groups are serialized first var groups = new List(); var remaining = new List(); var processed = new Dictionary(); foreach (var grp in this.Groups) { if (string.IsNullOrEmpty(grp.Group)) //Un-parented { groups.Add(grp); processed.Add(grp.Name, grp); } else { remaining.Add(grp); } } var indices = new List(); //Whittle down this list until all parents are resolved while (remaining.Count > 0) { indices.Clear(); //Collect the indices which can be added to the final list for (int i = 0; i < remaining.Count; i++) { var grp = remaining[i]; if (processed.ContainsKey(grp.Group)) //Parent of a group already processed indices.Add(i); } //Reverse iterate so that higher indices are removed first for (int i = indices.Count - 1; i >= 0; i--) { var index = indices[i]; var grp = remaining[index]; remaining.RemoveAt(index); groups.Add(grp); processed.Add(grp.Name, grp); } } foreach (var g in groups) g.Serialize(s); s.Write(this.Layers.Count); foreach (var t in this.Layers) t.Serialize(s); } /// /// Initializes this instance from the specified binary stream /// /// public void Deserialize(MgBinaryDeserializer d) { _disableChangeTracking = true; if (d.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) { if (d.ReadInt32() != MgBinaryVersion) throw new Exception("Invalid map version"); this.ResourceID = d.ReadResourceIdentifier(); } this.Name = d.ReadString(); this.ObjectId = d.ReadString(); this.MapDefinition = d.ReadResourceIdentifier(); this.CoordinateSystem = d.ReadString(); this.MapExtent = DeserializeExtents(d); var cc = d.ReadCoordinates(); if (this.ViewCenter != null) { this.ViewCenter.X = cc[0]; this.ViewCenter.Y = cc[1]; } else { this.ViewCenter = ObjectFactory.CreatePoint2D(cc[0], cc[1]); } this.ViewScale = d.ReadDouble(); this.DataExtent = DeserializeExtents(d); this.DisplayDpi = d.ReadInt32(); this.DisplayWidth = d.ReadInt32(); this.DisplayHeight = d.ReadInt32(); this.BackgroundColor = Utility.ParseHTMLColor(d.ReadString()); this.MetersPerUnit = d.ReadDouble(); if (d.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) this.LayerRefreshMode = d.ReadInt32(); var fds = new List(); int finiteScaleCount = d.ReadInt32(); while (finiteScaleCount-- > 0) fds.Add(d.ReadDouble()); _finiteDisplayScales = fds.ToArray(); m_changeList = new Dictionary(); if (d.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) { m_changeList = DeserializeChangeMap(d); if (d.SiteVersion >= new Version(2, 3)) { this.WatermarkUsage = d.ReadInt32(); } int mapLayerCount = d.ReadInt32(); if (mapLayerCount != 0) throw new Exception("On new versions, there should be no layer data in map"); } else { //ri.LayerGroupBlob = d.ReadStreamRepeat(d.ReadInt32()); DeserializeLayerData(d); m_changeList = DeserializeChangeMap(d); } _disableChangeTracking = false; } private static IEnvelope DeserializeExtents(MgBinaryDeserializer d) { int classid = d.ReadClassId(); if (d.SiteVersion <= SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classid != 18001) throw new Exception("Invalid class identifier, expected Box2D"); if (d.SiteVersion > SiteVersions.GetVersion(KnownSiteVersions.MapGuideEP1_1) && classid != 20001) throw new Exception("Invalid class identifier, expected Box2D"); int dimensions = d.ReadInt32(); if (dimensions != 2 && dimensions != 0) throw new Exception("Bounding box for map had " + dimensions.ToString() + " dimensions, 2 was expected"); double x1 = d.ReadDouble(); double y1 = d.ReadDouble(); double x2 = d.ReadDouble(); double y2 = d.ReadDouble(); double minx = Math.Min(x1, x2); double miny = Math.Min(y1, y2); double maxx = Math.Max(x1, x2); double maxy = Math.Max(y1, y2); return ObjectFactory.CreateEnvelope(minx, miny, maxx, maxy); } private Dictionary DeserializeChangeMap(MgBinaryDeserializer d) { Dictionary changes = new Dictionary(); int changeListCount = d.ReadInt32(); while (changeListCount-- > 0) { bool isLayer = d.ReadByte() > 0; string objid = d.ReadString(); ChangeList c = null; if (!changes.ContainsKey(objid)) { c = new ChangeList(objid, isLayer); changes.Add(c.ObjectId, c); } else { c = changes[objid]; } int changeCount = d.ReadInt32(); while (changeCount-- > 0) { //Split up to avoid dependency on argument evaluation order int ctype = d.ReadInt32(); c.Changes.Add(new Change((Change.ChangeType)ctype, d.ReadString())); } } return changes; } internal void DeserializeLayerData(MgBinaryDeserializer d) { int groupCount = d.ReadInt32(); this.Groups.Clear(); for (int i = 0; i < groupCount; i++) { //TODO: Review when we split to specific implementations RuntimeMapGroup g = new RuntimeMapGroup(); g.Deserialize(d); this.Groups.Add(g); } int mapLayerCount = d.ReadInt32(); this.Layers.Clear(); while (mapLayerCount-- > 0) { RuntimeMapLayer t = DeserializeLayer(d); this.Layers.Add(t); } } private RuntimeMapLayer DeserializeLayer(MgBinaryDeserializer d) { //TODO: Review when we split to specific implementations RuntimeMapLayer t = new RuntimeMapLayer(this); t.Deserialize(d); return t; } /// /// Gets the selection set /// /// The selection. public MapSelection Selection { get; private set; } /// /// Gets the group by its specified name /// /// The name. /// [Obsolete("Use the indexer of the Groups property instead")] public RuntimeMapGroup GetGroupByName(string name) { Check.NotNull(name, "name"); return this.Groups[name]; } /// /// Gets the layer by object id. /// /// The id. /// [Obsolete("Use the Layers property instead")] public RuntimeMapLayer GetLayerByObjectId(string id) { return this.Layers.GetByObjectId(id); } /// /// The collection of layers in this map /// public RuntimeMapLayerCollection Layers { get; private set; } /// /// The collection of groups in this map /// public RuntimeMapGroupCollection Groups { get; private set; } /// /// A cache of Layer Definition objects. Used to reduce lookup time of the same layer definitions /// protected Dictionary layerDefinitionCache = new Dictionary(); /// /// Adds the layer to the map. Does nothing if the layer instance is already in the map. /// /// /// [Obsolete("Use the Layers property instead")] internal void AddLayer(RuntimeMapLayer layer) { this.Layers.Add(layer); } /// /// Inserts the specified layer at the specified index. Does nothing /// if the layer instance is already in the map. /// /// /// [Obsolete("Use the Layers property instead")] public void InsertLayer(int index, RuntimeMapLayer layer) { this.Layers.Insert(index, layer); } /// /// Sets the layer to the specified index /// /// The index. /// The layer. [Obsolete("Use the Layers property instead")] public void SetLayerIndex(int index, RuntimeMapLayer layer) { this.Layers[index] = layer; } /// /// Removes the layer at the specified index /// /// The index. [Obsolete("Use the Layers property instead")] public void RemoveLayerAt(int index) { this.Layers.RemoveAt(index); } /// /// Gets the index of the specified layer /// /// /// [Obsolete("Use the Layers property instead")] public int IndexOfLayer(RuntimeMapLayer layer) { return this.Layers.IndexOf(layer); } /// /// Gets the index of the first layer whose name matches the specified name /// /// /// [Obsolete("Use the Layers property instead")] public int IndexOfLayer(string layerName) { Check.NotEmpty(layerName, "layerName"); var layer = this.Layers[layerName]; return this.Layers.IndexOf(layer); } /// /// Creates a new runtime layer from a layer definition. The created layer needs /// to be added to the map. /// /// /// /// [Obsolete("Use RuntimeMapLayer constructor")] public RuntimeMapLayer CreateLayer(string layerDefinitionId, RuntimeMapGroup group) { ILayerDefinition ldf = GetLayerDefinition(layerDefinitionId); var layer = new RuntimeMapLayer(this, ldf); if (group != null) layer.Group = group.Name; return layer; } private ILayerDefinition GetLayerDefinition(string layerDefinitionId) { ILayerDefinition ldf = null; if (layerDefinitionCache.ContainsKey(layerDefinitionId)) { ldf = layerDefinitionCache[layerDefinitionId]; } else { ResourceIdentifier.Validate(layerDefinitionId, ResourceTypes.LayerDefinition); ldf = (ILayerDefinition)this.ResourceService.GetResource(layerDefinitionId); layerDefinitionCache[layerDefinitionId] = ldf; } return ldf; } /// /// Creates the group and adds it to the map /// /// The name. /// internal RuntimeMapGroup AddGroup(string name) { var group = _mapSvc.CreateMapGroup(this, name); this.Groups.Add(group); return group; } /// /// Removes the specified layer. /// /// The layer. [Obsolete("Use the Layers property instead")] public void RemoveLayer(RuntimeMapLayer layer) { Check.NotNull(layer, "layer"); this.Layers.Remove(layer); } /// /// Removes the specified group. /// /// The group. [Obsolete("Use the Groups property instead")] public void RemoveGroup(RuntimeMapGroup group) { Check.NotNull(group, "group"); this.Groups.Remove(group); } /// /// Gets the layers of the specified group /// /// Name of the group. /// public RuntimeMapLayer[] GetLayersOfGroup(string groupName) { Check.NotEmpty(groupName, "groupName"); List layers = new List(); foreach (var lyr in this.Layers) { if (groupName.Equals(lyr.Group)) layers.Add(lyr); } return layers.ToArray(); } /// /// Saves this instance. The changes are propagated back to the MapGuide Server /// public virtual void Save() { Save(this.ResourceID); } /// /// A dummy resource, used for the runtime map /// internal const string RUNTIMEMAP_XML = ""; /// /// A dummy resource, used for the runtime map /// internal const string RUNTIMEMAP_SELECTION_XML = ""; private void Save(string resourceID) { var map = this; string selectionID = resourceID.Substring(0, resourceID.LastIndexOf(".")) + ".Selection"; this.ResourceService.SetResourceXmlData(resourceID, new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(RUNTIMEMAP_XML))); this.ResourceService.SetResourceXmlData(selectionID, new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(RUNTIMEMAP_SELECTION_XML))); ResourceIdentifier.Validate(resourceID, ResourceTypes.RuntimeMap); if (!resourceID.StartsWith("Session:" + this.SessionId + "//") || !resourceID.EndsWith(".Map")) throw new Exception("Runtime maps must be in the current session repository"); System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.IO.MemoryStream ms2 = null; //Apparently the name is used to reconstruct the resourceId rather than pass it around //inside the map server string r = map.Name; string t = map.ResourceID; string mapname = resourceID.Substring(resourceID.IndexOf("//") + 2); mapname = mapname.Substring(0, mapname.LastIndexOf(".")); map.Name = mapname; map.ResourceID = resourceID; try { map.Serialize(new MgBinarySerializer(ms, this.SiteVersion)); if (this.SiteVersion >= SiteVersions.GetVersion(KnownSiteVersions.MapGuideOS1_2)) { ms2 = new System.IO.MemoryStream(); map.SerializeLayerData(new MgBinarySerializer(ms2, this.SiteVersion)); } this.ResourceService.SetResourceData(resourceID, "RuntimeData", ResourceDataType.Stream, ms); if (ms2 != null) this.ResourceService.SetResourceData(resourceID, "LayerGroupData", ResourceDataType.Stream, ms2); SaveSelectionXml(resourceID); //Our changes have been persisted. Wipe our change list ClearChanges(); } finally { map.Name = r; map.ResourceID = t; } } private void SaveSelectionXml(string resourceID) { if (this.Selection == null) return; ResourceIdentifier.Validate(resourceID, ResourceTypes.RuntimeMap); string selectionID = resourceID.Substring(0, resourceID.LastIndexOf(".")) + ".Selection"; System.IO.MemoryStream ms = new System.IO.MemoryStream(); MgBinarySerializer serializer = new MgBinarySerializer(ms, this.SiteVersion); this.Selection.Serialize(serializer); ms.Position = 0; this.ResourceService.SetResourceData(selectionID, "RuntimeData", ResourceDataType.Stream, ms); } /// /// Gets the layer by its specified name /// /// The name. /// [Obsolete("Use the indexer of the Layer property instead")] public RuntimeMapLayer GetLayerByName(string name) { Check.NotEmpty(name, "name"); return this.Layers[name]; } #region change tracking //This mirrors the unmanaged implementation of MgMapBase //Turns out the 2.x implementation didn't track these map element changes internal void TrackChange(string objectId, bool isLayer, Change.ChangeType type, string param) { if (_disableChangeTracking) return; ChangeList changes = null; if (!m_changeList.ContainsKey(objectId)) { changes = new ChangeList(objectId, isLayer); m_changeList.Add(objectId, changes); } changes = m_changeList[objectId]; var change = new Change(type, param); changes.Changes.Add(change); Debug.WriteLine("Tracked change for " + (isLayer ? "Layer " : "Group ") + objectId + " (type: " + type + ", value: " + param + ")"); } /// /// Clears all tracked changes /// protected void ClearChanges() { m_changeList.Clear(); } /// /// Called when a group is removed /// /// internal void OnGroupRemoved(RuntimeMapGroup group) { //??? var layers = GetLayersOfGroup(group.Name); foreach (var lyr in layers) { this.Layers.Remove(lyr); } TrackChange(group.ObjectId, false, Change.ChangeType.removed, string.Empty); } /// /// Called when a group is added /// /// internal void OnGroupAdded(RuntimeMapGroup group) { //??? TrackChange(group.ObjectId, false, Change.ChangeType.added, string.Empty); } internal void OnGroupVisibilityChanged(RuntimeMapGroup group, string visbility) { TrackChange(group.ObjectId, false, Change.ChangeType.visibilityChanged, visbility); //??? } internal void OnGroupDisplayInLegendChanged(RuntimeMapGroup group, string displayInLegendState) { TrackChange(group.ObjectId, false, Change.ChangeType.displayInLegendChanged, displayInLegendState); } internal void OnGroupLegendLabelChanged(RuntimeMapGroup group, string legendLabel) { TrackChange(group.ObjectId, false, Change.ChangeType.legendLabelChanged, legendLabel); } internal void OnGroupParentChanged(RuntimeMapGroup group, string parentId) { TrackChange(group.ObjectId, false, Change.ChangeType.parentChanged, parentId); } internal void OnLayerRemoved(RuntimeMapLayer layer) { //??? TrackChange(layer.ObjectId, true, Change.ChangeType.removed, string.Empty); } internal void OnLayerAdded(RuntimeMapLayer layer) { //Fix the draw order of this layer that was added //??? TrackChange(layer.ObjectId, true, Change.ChangeType.added, string.Empty); } internal void OnLayerVisibilityChanged(RuntimeMapLayer layer, string visibility) { //??? TrackChange(layer.ObjectId, true, Change.ChangeType.visibilityChanged, visibility); } internal void OnLayerDisplayInLegendChanged(RuntimeMapLayer layer, string displayInLegendState) { TrackChange(layer.ObjectId, true, Change.ChangeType.displayInLegendChanged, displayInLegendState); } internal void OnLayerLegendLabelChanged(RuntimeMapLayer layer, string legendLabel) { TrackChange(layer.ObjectId, true, Change.ChangeType.legendLabelChanged, legendLabel); } internal void OnLayerParentChanged(RuntimeMapLayer layer, string parentId) { TrackChange(layer.ObjectId, true, Change.ChangeType.parentChanged, parentId); } internal void OnLayerSelectabilityChanged(RuntimeMapLayer layer, string selectability) { TrackChange(layer.ObjectId, true, Change.ChangeType.selectabilityChanged, selectability); } internal void OnLayerDefinitionChanged(RuntimeMapLayer layer) { TrackChange(layer.ObjectId, true, Change.ChangeType.definitionChanged, string.Empty); } #endregion #region convenience methods /// /// Convenience method for rendering a bitmap of the current map /// /// /// public System.IO.Stream Render(string format) { if (_mapSvc == null) throw new NotSupportedException(); return _mapSvc.RenderRuntimeMap( this.ResourceID, this.ViewCenter.X, this.ViewCenter.Y, this.ViewScale, this.DisplayWidth, this.DisplayHeight, this.DisplayDpi, format); } /// /// Convenience method for rendering a dynamic overlay of the current map /// /// /// /// public System.IO.Stream RenderDynamicOverlay(string format, bool keepSelection) { if (_mapSvc == null) throw new NotSupportedException(); return _mapSvc.RenderDynamicOverlay( this, this.Selection, format, keepSelection); } /// /// Convenience method for rendering the legend for this map /// /// /// /// /// /// public System.IO.Stream RenderMapLegend(int width, int height, System.Drawing.Color color, string format) { if (_mapSvc == null) throw new NotSupportedException(); return _mapSvc.RenderMapLegend( this, width, height, color, format); } #endregion } }