Set filter for layer at runtime in Autodesk Infrastructure Map Server or MapGuide Enterprise

By Daniel Du

When developing application on Autodesk Infrastructure Map Server, you may want to apply a filter to a layer to hide some features. In this post, I will demonstrate how to do this programmatically.

 

How to do in Infrastructure Studio UI

Firstly, let’s take a look how we can do the same using Infrastructure Studio UI. Open the layer in Infrastructure Studio, you will notice that you can set filter for layer by setting “Filter applied to data”, click the “…” button to open the expression editor, which helps you create filter string more easier. In this case, I set a filter for parcels layer:

Autogenerated_SDF_ID  > 10000

image

If you save the changes and click “refresh” button in layer preview, you will notice that the layer is filtered.

Where is this filter stored? It is stored in Infrastructure Map Server repository. An Infrastructure Map Server repository is a XML database that stores and manages the data for the site. The repository stores all data except data that is stored in external databases. Data stored in a repository is a resource. Let’s look at the resource content in MapAgent. Open the mapagent(http://localhost/mapserver2012/mapagent/index.html), select “Resource” from Services API and “GetResourceContent” from left-bottom frame, input the resource ID of parcel layer. For those who don’t know, you can right click the layer name in Site Explorer and select “properties” to get the resource id., Then click “submit” button, you will get the xml representation of the layer definition resource as following:
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<LayerDefinition xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221;
   mlns:xsd=”http://www.w3.org/2001/XMLSchema&#8221;
   xsi:noNamespaceSchemaLocation=”LayerDefinition-2.3.0.xsd”
   version=”2.3.0″>
<VectorLayerDefinition>
   <ResourceId>
     Library://Samples/Sheboygan/Data/Parcels.FeatureSource
  </ResourceId>
  <Watermarks />
  <FeatureName>SHP_Schema:Parcels</FeatureName>
  <FeatureNameType>FeatureClass</FeatureNameType>
  <Filter>
         Autogenerated_SDF_ID &gt; 10000
  </Filter>

The filter is stored in <Filter> section.  We did the modification from Infrastructure Studio, it applies to library repository, that means it affects all users.

 

Some background knowledge

There are two types of repository in Infrastructure Map Server: Library and Session. Persistent data that is available to all users is stored in the Library repository. In addition, each session has its own repository, which stores the run-time map state. It can also be used to store other data, like temporary layers that apply only to an individual session. For example, a temporary layer might be used to overlay map symbols indicating places of interest. Data in a session repository is destroyed when the session ends.

A resource identifier for a resource in the Library will always begin with Library://. For example:

Library://Samples/Layouts/SamplesPhp.WebLayout

A resource identifier for a session resource will always begin with Session:, followed by the session id. For example:

Session:70ea89fe-0000-1000-8000-005056c00008_en//layer.LayerDefinition

We can get the content of the specified resource by :

MgByteReader GetResourceContent(
                            MgResourceIdentifier resource);

And we can add a new resource to a resource repository, or updates an existing resource by:

virtual void SetResource(

MgResourceIdentifier resource,
MgByteReader content,
MgByteReader header);

 

How to do programmatically at runtime

Since we are going to set the filter at runtime, we will not want to change the library resource, otherwise it confuses other users. The solution is to make a copy of the layer as a temporary layer, and change the value of <Filter> tag programmatically, it only applies to the user who is setting the filter.

Now let’s do it step by step. Firstly create an web application, add references to Infrastructure Map Server Web Extension API dlls, and add a custom page names as “LayerFilter.aspx”, then add a custom command with “invoke URL” type and add it to web layout in Infrastructure Studio. If you are not familiar with this process, please refer to following DevTV:

Video : Autodesk® Infrastructure Map Server 2012 API Webcast
Recorded version of the Autodesk® Infrastructure Map Server 2012 API webcast
View online | Download

 

Here is the aspx code of “LayerFilter.aspx”, please note that we need to refresh the map to make it take effect after changing the layer, so we need

 

<body onload=”parent.parent.Refresh();”>

 

Here is the complete code :

Layer to filter: 
        Parcels
        

        

        Filter string:
        
            Autogenerated_SDF_ID > 10000
        
        

        try: Autogenerated_SDF_ID > 10000

Here is the code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using OSGeo.MapGuide;
 
namespace LayerFilterSample
{
    public partial class LayerFilter 
                            : System.Web.UI.Page
    {
        const string viewerPathSchema 
        = "http://localhost/mapserver2012/mapviewernet/"
        + "ajaxviewer.aspx?SESSION={0}&WEBLAYOUT={1}";
 
        Utility utility = new Utility();
        string mapName = "Sheboygan";
 
        protected void Page_Load(object sender, 
                                EventArgs e)
        {
            if (!IsPostBack)
            {
                string session = 
                    Request.Form.Get("SESSION");
 
                Session["MgSession"] = session;
                if (session != null)
                {
                    utility.InitializeWebTier(Request);
                    utility.ConnectToServer(session);
                }
            }
        }
 
 
        protected void tbOK_Click(object sender, 
                                    EventArgs e)
        {
            string layerName = tbLayer.Text;
            string strFilter = 
                Server.HtmlEncode(tbFilter.Text);
 
            string session = 
                Session["MgSession"].ToString();
 
            Utility utility = new Utility();
            utility.InitializeWebTier(Request);
            utility.ConnectToServer(session);
            MgSiteConnection siteConnection = 
                utility.GetSiteConnection();
 
            string webLayout = 
                Session["InitWebLayout"].ToString();
 
            string layerDefId = utility
                .GetLayerDefinitionResourceId(
                                layerName,
                                session,
                                mapName);
 
            //create a filtered session layer defination 
            string sessionLayerId = utility
                .SetFilterForLayer(session, 
                                    layerDefId, 
                                    strFilter);
 
            //create a session layer object, 
            //replace it with the library layer
            utility.ReplaceWithFilteredLayer(
                                    session,
                                    sessionLayerId,
                                    layerName, 
                                    mapName);
 
        }
    }
}

Here is the implementation of Utility class:

using System;
using System.Collections.Generic;
using System.Web;
using System.Collections;
using System.Xml;
using System.IO;
using System.Text;
 
using OSGeo.MapGuide;
 
 
/// 
/// Summary description for Utility.
/// Created by Daniel Du, DevTech
/// 
public class Utility
{
 
    MgSiteConnection siteConnection;
 
    public void InitializeWebTier(HttpRequest Request)
    {
        string realPath = 
            Request.ServerVariables["APPL_PHYSICAL_PATH"];
        String configPath = realPath + "../webconfig.ini";
        MapGuideApi.MgInitializeWebTier(configPath);
    }
 
    public void ConnectToServer(String sessionID)
    {
        MgUserInformation userInfo = 
            new MgUserInformation(sessionID);
        siteConnection = new MgSiteConnection();
        siteConnection.Open(userInfo);
    }
 
    public MgSiteConnection GetSiteConnection()
    {
        return siteConnection;
    }
 
    
    public MgByteSource ByteSourceFromXMLDoc(
                                XmlDocument xmlDoc)
    {
        //Save the amended DOM object to a memory stream
        MemoryStream XmlStream = new MemoryStream();
        xmlDoc.Save(XmlStream);
 
        //Now get the memory stream into a byte array 
        //that can be read into an MgByteSource
        byte[] byteNewDef = XmlStream.ToArray();
        String sNewDef = new String(Encoding.UTF8
                            .GetChars(byteNewDef));
        byte[] byteNewDef2 = 
                    new byte[byteNewDef.Length - 1];
        int iNewByteCount = Encoding.UTF8
            .GetBytes(sNewDef, 1, sNewDef.Length - 1,
                        byteNewDef2, 0);
        MgByteSource byteSource = 
            new MgByteSource(byteNewDef2, 
                            byteNewDef2.Length);
        byteSource.SetMimeType(MgMimeType.Xml);
        return byteSource;
    }
 
    public static string GetStringFromMemoryStream(
                                MemoryStream m)
    {
        if (m == null || m.Length == 0)
            return null;
 
        m.Flush();
        m.Position = 0;
        StreamReader sr = new StreamReader(m);
        string s = sr.ReadToEnd();
 
        return s;
    }
 
    public static MemoryStream 
        GetMemoryStreamFromString(string s)
    {
        if (s == null || s.Length == 0)
            return null;
 
        MemoryStream m = new MemoryStream();
        StreamWriter sw = new StreamWriter(m);
        sw.Write(s);
        sw.Flush();
 
        return m;
    }
 
 
 
 
    private static string GetXmlFromByteReader(
                            MgByteReader reader)
    {
        System.IO.MemoryStream ms = 
                    new System.IO.MemoryStream();
        byte[] buf = new byte[8 * 1024];
        int read = 1;
        while (read != 0)
        {
            read = reader.Read(buf, buf.Length);
            ms.Write(buf, 0, read);
        }
 
        string layoutXml = GetStringFromMemoryStream(ms);
        return layoutXml;
    }
 
    /// 
    /// use a layer in reposorty as template, 
    /// to create a temp layer 
    /// with filter, and set resource in repository
    /// 
    /// 
    /// the resource id 
    ///     of template layer defination 
    /// filter string
    /// resource id of temp layer in 
    ///     repository
    public string SetFilterForLayer(string sessionId, 
                                    string layerResId, 
                                    string strFilter)
    {
        if (siteConnection == null)
        {
            MgUserInformation userInfo = 
                new MgUserInformation(sessionId);
            siteConnection = new MgSiteConnection();
            siteConnection.Open(userInfo);
        }
 
        MgResourceIdentifier templateLayerId 
            = new MgResourceIdentifier(layerResId);
 
        MgResourceService resSvc = siteConnection
            .CreateService(MgServiceType.ResourceService) 
            as MgResourceService;
        //the resource content of LayerDefinition
 
        MgByteReader reader = resSvc
            .GetResourceContent(templateLayerId);
        string layoutXml = GetXmlFromByteReader(reader);
 
        //Edit the map resource in XmlDocument in DOM
 
        XmlDocument doc = new XmlDocument();
 
        doc.LoadXml(layoutXml);
 
        XmlNodeList objNodeList = 
            doc.SelectNodes(
                "//VectorLayerDefinition/Filter");
 
        if (objNodeList.Count > 0)
        {
            objNodeList.Item(0).InnerXml = strFilter;
        }
        else
        {
            XmlNode filterNode;
            filterNode = doc.CreateElement("Filter");
            filterNode.InnerText = strFilter;
            doc.GetElementsByTagName
                ("VectorLayerDefinition")[0]
                .AppendChild(filterNode);
        }
 
        MgByteSource byteSource = ByteSourceFromXMLDoc(doc);
        string sessionLayerName = templateLayerId.GetName();
        string sessionLayer = "Session:" + sessionId 
            + @"//" + sessionLayerName + ".LayerDefinition";
        MgResourceIdentifier sessionLayerResId 
            = new MgResourceIdentifier(sessionLayer);
 
        resSvc.SetResource(sessionLayerResId, 
                            byteSource.GetReader(), 
                            null);
 
        return sessionLayer;
    }
 
    /// 
    /// Create a MgLayer object using the session layer definition,
    /// Add it to MgMap and remove the original one
    /// 
    /// 
    /// 
    /// 
    /// 
    public void ReplaceWithFilteredLayer(   
                    string sessionId, 
                    string sessionLayerResId, 
                    string originalLayerName,
                    string mapName)
    {
        if (siteConnection == null)
        {
            MgUserInformation userInfo = 
                new MgUserInformation(sessionId);
            siteConnection = new MgSiteConnection();
            siteConnection.Open(userInfo);
        }
 
        MgResourceService resSvc = siteConnection
            .CreateService(MgServiceType.ResourceService) 
            as MgResourceService;
 
        MgResourceIdentifier filterLayerId = 
            new MgResourceIdentifier(sessionLayerResId);
        MgLayer filteredLayer = 
            new MgLayer(filterLayerId, resSvc);
 
        MgMap map = new MgMap();
        map.Open(resSvc, mapName);
 
        MgLayer oriLayer;
        try
        {
            oriLayer = map.GetLayers()
                .GetItem(originalLayerName) as MgLayer;
        }
        catch (MgObjectNotFoundException)
        {
            oriLayer = map.GetLayers()
                .GetItem(originalLayerName + "_filtered") 
                as MgLayer;
            originalLayerName = originalLayerName 
                + "_filtered";
        }
 
        filteredLayer.LegendLabel = 
             oriLayer.LegendLabel.Contains("_filtered")
            ? oriLayer.LegendLabel
            : oriLayer.LegendLabel + "_filtered";
        filteredLayer.Selectable = oriLayer.Selectable;
        filteredLayer.DisplayInLegend = 
                        oriLayer.DisplayInLegend;
        filteredLayer.Name = 
            oriLayer.Name.Contains("_filtered") 
            ? oriLayer.Name
            : oriLayer.Name + "_filtered";
        filteredLayer.Group = oriLayer.Group;
 
        int index = map.GetLayers()
                    .IndexOf(originalLayerName);
        map.GetLayers().RemoveAt(index);
        map.GetLayers().Insert(index, filteredLayer);
 
        map.Save(resSvc);
 
    }
 
      public string GetLayerDefinitionResourceId(
                            string layerName,
                            string sessionId, 
                            string mapName)
    {
        if (siteConnection == null)
        {
            MgUserInformation userInfo 
                = new MgUserInformation(sessionId);
            siteConnection = new MgSiteConnection();
            siteConnection.Open(userInfo);
        }
 
        MgResourceService resSvc = siteConnection
            .CreateService(MgServiceType.ResourceService) 
            as MgResourceService;
 
        MgMap map = new MgMap();
        map.Open(resSvc, mapName);
        MgLayerCollection layerColl = map.GetLayers();
        foreach (MgLayerBase ly in layerColl)
        {
            if (ly.Name == layerName 
                || ly.Name == layerName + "_filtered")
            {
                return ly.GetLayerDefinition().ToString();
            }
        }
 
        return string.Empty;
    }
 
    
}

I have intentionally not performed a lot of error checking or exception handling in the code below. This was to keep the code snippets as simple as possible.

 

How it looks like

Now I’d like to run this application and see how it looks like. Firstly I would like to set the filter string as “Autogenerated_SDF_ID > 0 ”, I can see all parcels are displayed:

image

Now I change the filter to Autogenerated_SDF_ID > 10000, you will see that some of the parcels are filtered:

image

At the same time, I open another browser(Firefox) to mimic another user, you will notice that the filter does not affect other users.

image

 

Hope this helps!

 


Comments

4 responses to “Set filter for layer at runtime in Autodesk Infrastructure Map Server or MapGuide Enterprise”

  1. I need some help getting my flexible layout to work on a xp and ie 8 machine. I get a fusionsf-compressed.js error

  2. Dear Daniel,
    thank you very much for this sample, and the explanations.
    I try to adapt this code to my needs, in AutoCAD Map 3D 2013.
    In SetFilterForLayer method, when executing the final SetResource, I get an Exception telling me that the XML Layer Definition is incorrect. This Exception occurs even if I bypass the Xml Content modification (by appending the child filterNode). Even with the original XML content, I get the Exception.
    OSGeo.MapGuide.MgInvalidArgumentException: Argument(s) incorrect(s):
    La définition du calque XML est incorrecte.
    à OSGeo.MapGuide.MapApiPINVOKE.SWIGExceptionHelper.ThrowCustomException(String className, IntPtr cptr)
    à OSGeo.MapGuide.MapApiPINVOKE.AcMapResourceService_SetResource(IntPtr jarg1, IntPtr jarg2, IntPtr jarg3, IntPtr jarg4)
    à Autodesk.Gis.Map.Platform.AcMapResourceService.SetResource(MgResourceIdentifier resource, MgByteReader content, MgByteReader header)
    à Takm.AutoCadMap3D2013.TakmAutoCadHelper.SetFilterForLayer(String layerResId, String strFilter) dans D:\TAKM_PRODUCT\trunk\takm.AutoCAD_MAP_3D_2013\TakmAutocadHelper.cs:ligne 149
    à Takm.AutoCadMap3D2013.TakmAutoCad.TakmTestFilter() dans D:\TAKM_PRODUCT\trunk\takm.AutoCAD_MAP_3D_2013\TAKMAutoCAD.cs:ligne 663
    à Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorker(MethodInfo mi, Object commandObject, Boolean bLispFunction)
    à Autodesk.AutoCAD.Runtime.CommandClass.InvokeWorkerWithExceptionFilter(MethodInfo mi, Object commandObject, Boolean bLispFunction)
    à Autodesk.AutoCAD.Runtime.PerDocumentCommandClass.Invoke(MethodInfo mi, Boolean bLispFunction)
    à Autodesk.AutoCAD.Runtime.CommandClass.CommandThunk.Invoke()
    Below is the code I adapted from your one :
    public static String SetFilterForLayer(String layerResId, String strFilter)
    {
    MgResourceIdentifier templateLayerId = new MgResourceIdentifier(layerResId);
    MgResourceService resSvc = AcMapServiceFactory.GetService(MgServiceType.ResourceService) as MgResourceService;
    MgByteReader reader = resSvc.GetResourceContent(templateLayerId);
    string layoutXml = GetXmlFromByteReader(reader);
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(layoutXml);
    XmlNodeList objNodeList = doc.SelectNodes(“//VectorLayerDefinition/Filter”);
    if (objNodeList.Count > 0)
    {
    objNodeList.Item(0).InnerXml = strFilter;
    }
    else
    {
    XmlNode filterNode;
    filterNode = doc.CreateElement(“Filter”);
    filterNode.InnerText = strFilter;
    doc.GetElementsByTagName(“VectorLayerDefinition”)[0].AppendChild(filterNode);
    }
    MgByteSource byteSource = ByteSourceFromXMLDoc(doc);
    string sessionLayerName = templateLayerId.GetName();
    string sessionLayer = @”Library://” + sessionLayerName + “.LayerDefinition”;
    MgResourceIdentifier sessionLayerResId = new MgResourceIdentifier(sessionLayer);
    resSvc.SetResource(sessionLayerResId,byteSource.GetReader(),null);
    return sessionLayer;
    }
    Do you have any idea ?
    Thanks a lot again ;)
    tom.

  3. Hi Tom,
    For AutoCAD Map 3D, there is no concept of “session layer”, so you cannot use code snippet like below:
    string sessionLayerName = templateLayerId.GetName();
    string sessionLayer = @”Library://” + sessionLayerName + “.LayerDefinition”;
    MgResourceIdentifier sessionLayerResId = new MgResourceIdentifier(sessionLayer);
    resSvc.SetResource(sessionLayerResId,byteSource.GetReader(),null);
    You have to change the layer definition of library resources.
    PS. blog is not a good place for technical support, if you have question, you are recommended to go to forums.autodesk.com or ADN if you are a member.

  4. Anil Kumar K B Avatar
    Anil Kumar K B

    Hi, Could you please help me on the query below.
    For displaying the pole symbol over PGIS Browser Web Application i have configured the pole layer in MapGuide Studio and when i am trying to set the query conditions like POLE_CLASS =’NULL’ to display the pole symbol for the pole_class null its not reflecting over Application.
    But for other query conditions like POLE_CLASS in (‘SB’, ‘CB’,’SWR’) its working fine.
    Could you please help me on this.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading