Hexagon Geospatial
MENU

WebGIS Tutorials

WebGIS enables powerful geospatial web applications and services that securely share your organization’s rich geospatial data, and provides tools to deeply examine spatial data and create value added products, on demand.
Showing results for 
Search instead for 
Do you mean 

How to add application/json format to WMS GetFeatureInfo

by Technical Evangelist ‎02-05-2019 05:07 AM - edited ‎02-05-2019 05:08 AM (288 Views)

Introduction

WMS service based on GeoMedia WebMap (version 2018) supports only the following WMS Feature Info output formats:

  • text/xml
  • text/html
  • application/gml+xml; version=3.1

Modern client applications tend to favor JSON data which is easier to manipulate wherever JavaScript language takes a place. Fortunately enough, it is possible to extend WebMap's WMS using a custom pipe, adding support for any output format required. In this article, we will cover steps necessary to add support of application/json output for the GetFeatureInfo request.

Prerequisites

  • Although not really necessary, it is recommended to have Visual Studio (2013 or later) installed
  • General understanding of SDI pipes architecture, described in Developing Custom Pipes article

Implementation

The implementation of the added functionality is straightforward. The basic workflow is as following:

  1. User sends feature info request such as: ?request=GetFeatureInfo&....&info_format=application/json
  2. Custom GetFeatureInfo handler changes the format to internally understandable text/xml and pass the request further down for internal processing, e.g. to GWMOriginatingPipe
  3. Custom GetFeatureInfo handler acquires a returning XML response from the originating pipe and transforms it using an XSL stylesheet into JSON format
  4. Custom GetFeatureInfo handler changes the response type to application/json and writes the output stream (JSON data) to the response.

Because we want our transformations to be very flexible and easy to adjust as per WMS client needs, a simple approach using XSL transformation was adopted. This approach is very close to the currently implemented XML to HTML transformations.

For an easy start it is recommended to use the ExamplePipe project from the Developers KB article referenced above. In that project, there is a couple of things to adjust.

Rewrite or create new pipe configuration class (CustomFIPipeConfiguration)

Our custom feature info pipe will read a XSL file which is recommended to put into XMLTemplates subfolder of a WMS instance. Therefore, we need to create a property that will read the path value defined by the administrator. Example usage:

<CustomFIPipe name="customFeatureInfoPipe" 
nextPipeName="gwmOrigPipe"
firstPipe="false"
featureInfoXslPath="XMLTemplates/XmlToJsonFeatureInfo.xsl"/>

Class implementation:

using Intergraph.GeoMedia.Web.SDI.Common.BasePipe.PipesConfiguration;
using System.Configuration;

namespace Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe
{
   /// <summary>
   ///  Configuration class for <see cref="ExamplePipe"/>.
   /// </summary>
   public class CustomFIPipeConfiguration : PipeConfiguration
   {
      /// <summary>
      /// Example configuration property. Tag is used to distinguish between different ExamplePipes.
      /// </summary>
      [ConfigurationProperty("featureInfoXslPath", IsRequired = true)]
      public string XSLTPath
      {
         get { return (string)this["featureInfoXslPath"]; }
         set { this["featureInfoXslPath"] = value; }
      }
   }
}

Rewrite or create new custom pipe class (CustomFIPipe)

As stated above, our custom pipe will hook up onto GetFeatureInfo request type and process it by its own. It must also allow to pass through all other requests such as GetMap and GetCapabilities. Therefore, following methods will be required.

Initialize

Overridden Initialize method will only set up a path for the XSL file defined in web.config pipe configuration

public override void Initialize(PipeConfiguration config, IPipe nextPipe)
{
   _pipeConfiguration = (CustomFIPipeConfiguration)config;
   xsltPath = Path.Combine(config.RootFilePath, _pipeConfiguration.XSLTPath);
   
   base.NextPipe = nextPipe;    
} 

GetFeatureInfoHandler

This is the most interesting, yet a simple part. Custom GetFeatureInfo handler implementation will read the request parameters, manipulate them to be accepted by the lower-level pipes and once response is ready, it will translate it from XML to JSON. The sample implementation was done only for WMS version 1.3.0 but should be similar (if not the same) for version 1.1.1.

[SDIMethodHandler("GetFeatureInfo", "1.3.0", "wms")]
[SDIMethodHandler("GetFeatureInfo", "1.1.1", "wms")]
public IResponseBase GetFeatureInfoHandler(SDIMethodParameters parameters,
     AdditionalParametersCollection additionalParameters,
     IUserContext userContext)
{
   IResponseBase response = null;

   // TODO: Split into two distinct GetFeatureInfoHandler
   if (parameters is GetFeatureInfoParameters111 fi111)
   {
      if (fi111.InfoFormat == "application/json")
         throw new NotImplementedException("Custom FeatureInfo handler for version 1.1.1 not implemented.");
   }

   if (parameters is GetFeatureInfoParameters130 fi130)
   {
      if (fi130.InfoFormat == "application/json")
      {
         fi130.InfoFormat = "text/xml";
         response = InvokeOnNextPipe(parameters, additionalParameters, userContext);
         response = ProcessResponse(response);

         return response;
      }
   }

   response = InvokeOnNextPipe(parameters, additionalParameters, userContext);
   return response;
}
IResponseBase ProcessResponse(IResponseBase response)
{                  
   var xdoc = XDocument.Load(response.ResponseStream);

   StringBuilder output = new StringBuilder();
   XmlWriterSettings writerSettings = new XmlWriterSettings
   {
      OmitXmlDeclaration = true,
      ConformanceLevel = ConformanceLevel.Fragment
   };

   using (XmlWriter transformedData = XmlWriter.Create(output, writerSettings))
   {
      XslCompiledTransform transform = new XslCompiledTransform();
      
      transform.Load(xsltPath);
      transform.Transform(xdoc.CreateReader(), transformedData);            
   }
               
   var resultResponse = new StringResponse(output.ToString(), "application/json");
   resultResponse.Headers.Add(response.Headers);
   resultResponse.HttpCode = response.HttpCode;

   return resultResponse;
}

OtherMethodsHandler

This is only a pass-through handler as we're not going to manipulate other request / response types.

[SDIMethodHandler("GetMap", "1.1.1", "wms")]
[SDIMethodHandler("GetMap", "1.3.0", "wms")]
[SDIMethodHandler("GetCapabilities", "1.1.1", "wms")]
[SDIMethodHandler("GetCapabilities", "1.3.0", "wms")]
public IResponseBase OtherMethodsHandler(SDIMethodParameters parameters,
                        AdditionalParametersCollection additionalParameters,
                        IUserContext userContext)
{
   return InvokeOnNextPipe(parameters, additionalParameters, userContext);
}

XSL Template

This template is used to convert from a WebMap's specific WMS Feature Info XML response to its JSON representation. It is recommended to put this template into XMLTemplates subfolder.

<xsl:stylesheet xmlns:def="http://www.intergraph.com/geomedia/gml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0">
  <xsl:template match="/">
    {
    "type": "FeatureCollection",
    "features":
    [<xsl:for-each select="def:FeatureCollection/gml:featureMember">
      {
      "type": "Feature",
      "geometry": null,
      "layerName":"<xsl:value-of select="def:Layer/@Name"/>",
      "properties":
      {<xsl:for-each select="def:Layer/def:Attribute">
        "<xsl:value-of select="@Name"/>":<xsl:choose>
          <xsl:when test="number(current()) = current()">
            <xsl:value-of select="number(current())"/>
          </xsl:when>
          <xsl:otherwise>
            "<xsl:value-of select="current()"/>"
          </xsl:otherwise>
        </xsl:choose><xsl:choose>
          <xsl:when test="position() != last()">,</xsl:when>
        </xsl:choose>
      </xsl:for-each>
      }
      }<xsl:choose>
        <xsl:when test="position() != last()">,</xsl:when>
      </xsl:choose>
    </xsl:for-each>]
    }
  </xsl:template>
</xsl:stylesheet>

Final modifications

For the client application to recognize that our WMS now supports a new format, related change shall be done in ...\XMLTemplates\WMS_Capabilities_1_3_0.xml file:

 

...
<GetFeatureInfo>
   <Format>text/xml</Format>
   <Format>text/html</Format>
   <Format>application/gml+xml; version=3.1</Format>
   <Format>application/json</Format>
...

Now just build the project, place the generated DLL into WMS instance \bin folder and reference it in web.config. It is suggested to add this pipe past MetadataConfigurationPipe (if used) in the pipe chain. More details on this can be found in the Developing Custom Pipes article.

<sectionGroup name="pipes">
    ...
    <section name="CustomFIPipe" type="Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe.CustomFIPipeConfiguration, Intergraph.GeoMedia.Web.SDI.Pipes.ExamplePipe"/>
</sectionGroup>

...
<CustomFIPipe name="customFeatureInfoPipe" nextPipeName="gwmOrigPipe" firstPipe="false" featureInfoXslPath="XMLTemplates/XmlToJsonFeatureInfo.xsl"/>
...

Samples

Sample request:

.../WMSTest/service.svc/get
  ?bbox=-13927381.678361,2352208.31620937,-6892729.09121969,6671817.65866125
  &request=GetFeatureInfo
  &service=WMS
  &CRS=EPSG:3857
  &feature_count=10
  &format=image/png
  &height=883

  &width=1438

  &i=371
  &j=268
  &info_format=text/xml
  &layers=States11,Counties
  &query_layers=States11,Counties
  &styles=,
  &version=1.3.0

 

XML response:

<?xml version="1.0" encoding="utf-8"?>
<FeatureCollection xmlns="http://www.intergraph.com/geomedia/gml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
   <gml:featureMember>
      <Layer Name="Counties">
         <Attribute Name="ID">3094</Attribute>
         <Attribute Name="FIPS">56013</Attribute>
         <Attribute Name="COUNTY_NAME">FREMONT</Attribute>
         <Attribute Name="STATE_FIPS">56</Attribute>
         <Attribute Name="STATE_NAME">Wyoming</Attribute>
         <Attribute Name="POP">33662</Attribute>
         <Attribute Name="WHPOP">26852</Attribute>
         <Attribute Name="BLKPOP">17</Attribute>
         <Attribute Name="HISPPOP">1173</Attribute>
         <Attribute Name="ASIANPOP">136</Attribute>
         <Attribute Name="MEDAGEMAL">31.6</Attribute>
         <Attribute Name="MEDAGEFEM">33.5</Attribute>
         <Attribute Name="POP_EDU">20645</Attribute>
         <Attribute Name="ED_LT_HS">4646</Attribute>
         <Attribute Name="EDHS">6973</Attribute>
         <Attribute Name="EDSOMCOL">4369</Attribute>
         <Attribute Name="EDCOL">3586</Attribute>
         <Attribute Name="EDGRAD">1071</Attribute>
         <Attribute Name="SQMILES">9181.1</Attribute>
         <Attribute Name="TOTCRIME">97</Attribute>
         <Attribute Name="AVETEMP">44.6</Attribute>
         <Attribute Name="ANNULRAIN">13</Attribute>
         <Attribute Name="ANNULSNOW">102</Attribute>
      </Layer>
   </gml:featureMember>
   <gml:featureMember>
      <Layer Name="States11">
         <Attribute Name="ID">49</Attribute>
         <Attribute Name="FIPS">56</Attribute>
         <Attribute Name="STATE_NAME">Wyoming</Attribute>
         <Attribute Name="STAABBRV">WY</Attribute>
         <Attribute Name="ST_PLACE">5600000</Attribute>
         <Attribute Name="ST_COUNTY">56000</Attribute>
         <Attribute Name="POP">453588</Attribute>
         <Attribute Name="WHPOP">427220</Attribute>
         <Attribute Name="BLKPOP">3290</Attribute>
         <Attribute Name="HISPPOP">24976</Attribute>
         <Attribute Name="ASIANPOP">2742</Attribute>
         <Attribute Name="MEDAGEMAL">31.2</Attribute>
         <Attribute Name="MEDAGEFEM">32.3</Attribute>
         <Attribute Name="POP_EDU">277769</Attribute>
         <Attribute Name="ED_LT_HS">47113</Attribute>
         <Attribute Name="EDHS">92081</Attribute>
         <Attribute Name="EDSOMCOL">67231</Attribute>
         <Attribute Name="EDCOL">55503</Attribute>
         <Attribute Name="EDGRAD">15841</Attribute>
         <Attribute Name="SQMILES">96989.1</Attribute>
         <Attribute Name="TOTCRIME">50</Attribute>
         <Attribute Name="AVETEMP">44.9</Attribute>
         <Attribute Name="ANNULRAIN">13</Attribute>
         <Attribute Name="ANNULSNOW">86</Attribute>
      </Layer>
   </gml:featureMember>
</FeatureCollection>

Sample JSON response after changing &info_format=application/json

{
   "type": "FeatureCollection",
   "features": [{
         "type": "Feature",
         "geometry": null,
         "layerName": "Counties",
         "properties": {
            "ID": 3094,
            "FIPS": 56013,
            "COUNTY_NAME": "FREMONT",
            "STATE_FIPS": 56,
            "STATE_NAME": "Wyoming",
            "POP": 33662,
            "WHPOP": 26852,
            "BLKPOP": 17,
            "HISPPOP": 1173,
            "ASIANPOP": 136,
            "MEDAGEMAL": 31.6,
            "MEDAGEFEM": 33.5,
            "POP_EDU": 20645,
            "ED_LT_HS": 4646,
            "EDHS": 6973,
            "EDSOMCOL": 4369,
            "EDCOL": 3586,
            "EDGRAD": 1071,
            "SQMILES": 9181.1,
            "TOTCRIME": 97,
            "AVETEMP": 44.6,
            "ANNULRAIN": 13,
            "ANNULSNOW": 102
         }
      }, {
         "type": "Feature",
         "geometry": null,
         "layerName": "States11",
         "properties": {
            "ID": 49,
            "FIPS": 56,
            "STATE_NAME": "Wyoming",
            "STAABBRV": "WY",
            "ST_PLACE": 5600000,
            "ST_COUNTY": 56000,
            "POP": 453588,
            "WHPOP": 427220,
            "BLKPOP": 3290,
            "HISPPOP": 24976,
            "ASIANPOP": 2742,
            "MEDAGEMAL": 31.2,
            "MEDAGEFEM": 32.3,
            "POP_EDU": 277769,
            "ED_LT_HS": 47113,
            "EDHS": 92081,
            "EDSOMCOL": 67231,
            "EDCOL": 55503,
            "EDGRAD": 15841,
            "SQMILES": 96989.1,
            "TOTCRIME": 50,
            "AVETEMP": 44.9,
            "ANNULRAIN": 13,
            "ANNULSNOW": 86
         }
      }
   ]
}
Contributors