Parsing GetCapabilities Response

Editor
Sep 22, 2011 at 11:48 PM

I've been searching for the perfect GetCapabilities response parser.  Have not found one.  What BruTile has is pretty close.  I have been making modifications to a local copy; some mods are OK, some are not so pretty.  I'd like to contribute the useful changes back to BruTile if possible.  Here are the issues I ran into and some solutions:

  1. BoundingBoxes - My application needs to access the BoundingBoxes for each layer.  From what I can tell, BB's were optional on 1.1.1 and are required on 1.3.0.  So, I defined a new WmsLayerBoundingBox type and declared a WmsLayerBoundingBox[] on the WmsServerLayer type.  If no BoundingBoxes are found, it will be a zero-length array.  This code was actually pretty clean and fit in well with the rest of BruTile, so I think that is probably OK to contribute.
  2. I had a need to construct a WmsCapabilities from binary data instead of querying or constructing from a file.  So, I added a constructor that took a Stream as input.  I think this is probably OK to contribute as well because it is only 2 lines of code.
  3. Some WMS Servers have multiple Layer elements at the top level.  From what I can discern from the WMS specification and also from the XML schema, this is probably a specification violation.  I did not make any changes to BruTile to accommodate this.  Since WmsCapabilities exposes a single WmsServerLayer object, I think the only way to accommodate might be to create an artificial single root WmsServerLayer and have the top level layers from the XML Response be children of the artificial layer.  I am torn on this, because specs should be adhered to.  I was wondering if anyone else has seen this.
  4. I'm not an XML expert.  Some Capabilities responses that I tried to parse had namespace issues.  Basically, any node selection involving the "sm" namespace returned Null.  This meant I'd get the "Service Tag not found error".  I tried various things to address this.  Maybe someone who reads this knows a solution.  Again, this is probably happening because the WMS Server is not adhering to the specification.  However, I'd really like to be able to use some of these servers, so I came up with a solution that worked, but was not pretty.  It is described below:
    1. Added a bool to the WmsCapabilities class called _hasWmsNamespace.
    2. At the top of ParseCapabilities(), I put the following line:
      _hasWmsNamespace = !string.IsNullOrEmpty(doc.DocumentElement.NamespaceURI);
    3. I also added the following method: 
             /// <summary>
              /// Strip namespace prefix from supplied path if the namespace is not present in the document
              /// </summary>
              /// <param name="path"></param>
              /// <returns></returns>
              private string NormalizePath(string path)
              {
                  string normalized = path;
                  if (!_hasWmsNamespace)
                      normalized = path.Replace("sm:", "");
                  return normalized;
              }
      
    4. Then, everywhere I did a select involving the sm prefix, I did something like this: 
      XmlNode xnService = doc.DocumentElement.SelectSingleNode(NormalizePath("sm:Service"), _nsmgr);
      

Well, that is all for now.  I'll be glad to provide more information or XML data if someone has the time to look at it.  I am a developer on DotSpatial and have had some comms with FObermaier over there.  I'd be interested in your comments.
Thanks,
kellison

Developer
Sep 23, 2011 at 6:22 AM

Hello Kyle,

have you had a look at sharpmap's WMS client (http://sharpmap.codeplex.com/SourceControl/changeset/view/92810#1376763)

It's ParseCapabilities() function just adds "sm" to the NamespaceManager.

        /// <summary>
        /// Parses a servicedescription and stores the data in the ServiceDescription property
        /// </summary>
        public void ParseCapabilities()
        {
            if (_xmlDoc.DocumentElement.Attributes["version"] != null)
            {
                _version = _xmlDoc.DocumentElement.Attributes["version"].Value;
                if (_version != "1.0.0" && _version != "1.1.0" && _version != "1.1.1" && _version != "1.3.0")
                    throw new ApplicationException("WMS Version " + _version + " is not currently supported");

                _nsmgr.AddNamespace(String.Empty, "http://www.opengis.net/wms");
                if (_version == "1.3.0")
                {
                    _nsmgr.AddNamespace("sm", "http://www.opengis.net/wms");
                }
                else
                    _nsmgr.AddNamespace("sm", "");

                _nsmgr.AddNamespace("xlink", "http://www.w3.org/1999/xlink");
                _nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            }
            else
                throw (new ApplicationException("No service version number was found in the capabilities XML file!"));

            XmlNode xnService = _xmlDoc.DocumentElement.SelectSingleNode("sm:Service", _nsmgr);
            XmlNode xnCapability = _xmlDoc.DocumentElement.SelectSingleNode("sm:Capability", _nsmgr);
            if (xnService != null)
                ParseServiceDescription(xnService);
            else
                throw (new ApplicationException("No service tag found in the capabilities XML file!"));


            if (xnCapability != null)
                ParseCapability(xnCapability);
            else
                throw (new ApplicationException("No capability tag found in the capabilities XML file!"));
        }
Editor
Sep 23, 2011 at 2:15 PM

Thankyou FObermaier,

You nailed it!  Actually, BruTile was doing what SharpMap did, but the magic is to not only check the version, but to also check DocumentElement.NamespaceURI.  So, here is the slight modification that fixed it

                if (_wmsVersion == "1.3.0" && !string.IsNullOrEmpty(doc.DocumentElement.NamespaceURI))
                    _nsmgr.AddNamespace("sm", "http://www.opengis.net/wms");
                else
                    _nsmgr.AddNamespace("sm", "");
Kyle

 

 

Coordinator
Sep 24, 2011 at 11:47 AM

Hi Kyle,

Thanks for the contributions!

I added a simple unit test for WmsCapabilties. Maybe you can add some more tests for the significant changes you added. And perhaps also test if existing functionality does not fail.

http://brutile.codeplex.com/SourceControl/changeset/view/a038bfd028e2#BruTile.Tests%2fWeb%2fWmsCapabilitiesTest.cs

I added you as a developer. So you can do it directly yourself.

There is one issue with the WmsCapabilities, it is using XmlDocument instead of XDocument, therefore it does not work with Silverlight.

There is also another issue, and that is that is is based on old SharpMap code and therefore is on a LGPL license. I was thinking to rewrite it entirely from scratch to loose the LGPL, and then use XDocument. This is big chance and I won't have time for it soon. But something you have to keep in mind.

cheers,

Paul

Editor
Sep 26, 2011 at 1:29 PM

Thanks Paul.  Will do.  It may be a little while before I actually make the post as in a time crunch at the moment.  The amount of changes I will be making is probably negligible with respect to the refactoring effort from XMLDocument to XDocument.

Thanks,

Kyle