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

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"
mlns:xsd="http://www.w3.org/2001/XMLSchema"
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 > 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 :
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="LayerFilter.aspx.cs"
Inherits="LayerFilterSample.LayerFilter" %>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body onload="parent.parent.Refresh();">
<form id="form1" runat="server">
<div>
Layer to filter:
<asp:TextBox ID="tbLayer" runat="server">Parcels</asp:TextBox>
<br />
<br />
Filter string:
<asp:TextBox ID="tbFilter" runat="server" Width="387px">
Autogenerated_SDF_ID > 10000
</asp:TextBox>
<br />
try: Autogenerated_SDF_ID > 10000<br />
<br />
<asp:Button ID="tbOK" runat="server" onclick="tbOK_Click"
Text="OK" Width="94px" />
</div>
</form>
</body>
</html>
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>
/// Summary description for Utility.
/// Created by Daniel Du, DevTech
/// </summary>
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;
}
/// <summary>
/// use a layer in reposorty as template,
/// to create a temp layer
/// with filter, and set resource in repository
/// </summary>
/// <param name="map"></param>
/// <param name="layerResId">the resource id
/// of template layer defination </param>
/// <param name="strFilter">filter string</param>
/// <returns>resource id of temp layer in
/// repository</returns>
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;
}
/// <summary>
/// Create a MgLayer object using the session layer definition,
/// Add it to MgMap and remove the original one
/// </summary>
/// <param name="sessionId"></param>
/// <param name="sessionLayerResId"></param>
/// <param name="originalLayerName"></param>
/// <param name="mapName"></param>
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:

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

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

Hope this helps!