MapScript Wrappers for WxS Services

Author:

Frank Warmerdam

Contact:

warmerdam at pobox.com

Introduction

With the implementation of MapServer MS RFC 16: MapScript WxS Services in MapServer 4.9, MapScript now has the ability to invoke MapServer’s ability to execute OGC Web Service requests such as WMS, WCS, and WFS as well as capturing the results of processing the requests.

This makes it possible to dynamically configure a map object based on information in the original request, and to capture the output of processing requests for further post-processing.

Avertissement

MapServer CGI Controls and Run-time Substitution are not applied when using mapscript WxS wrappers. It is up to the mapscript code you are writing to apply any mapfile modifications.

Python Examples

The following trivial example, in Python, demonstrates a script that internally provides the map name, but otherwise uses normal MapServer processing.

import mapscript

req = mapscript.OWSRequest()
req.loadParams()

map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )
map.OWSDispatch( req )

The OWSRequest object is used to manage a parsed list of OWS processing options. In the above example they are loaded from the environment using the loadParams() call which fetches and parses them from QUERY_STRING in the same way the mapserv executable would.

Then we load a map, and invoke OWSDispatch with the given arguments on that map. By default the results of the dispatched request are written to stdout which returns them back to the client.

The following example ignores all passed in arguments, and manually constructs a request argument by argument. It is likely more useful for testing purposes than for deploying WxS services, but demonstrates direct manipulation of the request object.

import mapscript

req = mapscript.OWSRequest()
req.setParameter( 'SERVICE', 'WMS' )
req.setParameter( 'VERSION', '1.1.0' )
req.setParameter( 'REQUEST', 'GetCapabilities' )

map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )
map.OWSDispatch( req )

The previous example have all let results be returned directly to the client. But in some cases we want to be able to capture, and perhaps modify the results of our requests in some custom way. In the following example we force the hated OGC required mime type for errors to simple text/xml (warning - non-standard!)

import mapscript

req = mapscript.OWSRequest()
req.loadParams()

map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )

mapscript.msIO_installStdoutToBuffer()
map.OWSDispatch( req )

content_type = mapscript.msIO_stripStdoutBufferContentType()
content = mapscript.msIO_getStdoutBufferBytes()

if content_type == 'vnd.ogc.se_xml':
  content_type = 'text/xml'

print 'Content-type: ' + content_type
print
print content

This example demonstrates capture capturing output of OWSRequest to a buffer, capturing the « Content-type: » header value, and capturing the actual content as binary data. The msIO_getStdoutBufferBytes() function returns the stdout buffer as a byte array. If the result was known to be text, the msIO_getStdoutBufferString() function could have been used to fetch it as a string instead, for easier text manipulation.

Perl Example

Most of the same capabilities are accessible in all SWIG based mapscript languages. In perl, we could script creation of a request like this:

#!/usr/bin/perl

use mapscript;

$req = new mapscript::OWSRequest();
$req->setParameter( "SERVICE", "WMS" );
$req->setParameter( "VERSION", "1.1.0" );
$req->setParameter( "REQUEST", "GetCapabilities" );

$map = new mapscript::mapObj( "/u/www/maps/ukpoly/ukpoly.map" );

mapscript::msIO_installStdoutToBuffer();

$dispatch_out = $map->OWSDispatch( $req );

printf "%s\n", mapscript::msIO_getStdoutBufferString();

One issue in Perl is that there is currently no wrapping for binary buffers so you cannot call msIO_getStdoutBufferBytes(), and so cannot manipulate binary results.

More Perl example code

#!/usr/bin/perl
############################################################################
#
# Name:     wxs.pl
# Project:  MapServer
# Purpose:  MapScript WxS example
#
# Author:   Tom Kralidis
#
##############################################################################
#
# Copyright (c) 2007, Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
############################################################################/

use CGI::Carp qw(fatalsToBrowser);
use mapscript;
use strict;
use warnings;
use XML::LibXSLT;
use XML::LibXML;

my $dispatch;

# uber-trivial XSLT document, as a file
my $xsltfile = "/tmp/foo.xslt";

# here's the actual document inline for
# testing save and alter $xsltFile above

=comment
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:wfs="http://www.opengis.net/wfs">
 <xsl:output method="xml" indent="yes"/>
 <xsl:template match="/">
  <WFSLayers>
   <xsl:for-each select="//wfs:FeatureType">
    <wfs_layer>
     <name><xsl:value-of select="wfs:Name"/></name>
     <title><xsl:value-of select="wfs:Title"/></title>
    </wfs_layer>
   </xsl:for-each>
  </WFSLayers>
 </xsl:template>
</xsl:stylesheet>
=cut

my $mapfile  = "/tmp/config.map";
# init OWSRequest object
my $req = new mapscript::OWSRequest();

# pick up CGI parameters passed
$req->loadParams();

# init mapfile
my $map = new mapscript::mapObj($mapfile);

# if this is a WFS GetCapabilities request, then intercept
# what is normally returned, process with an XSLT document
# and then return that to the client
if ($req->getValueByName('REQUEST') eq "GetCapabilities" && $req->getValueByName('SERVICE') eq "WFS") {

  # push STDOUT to a buffer and run the incoming request
  my $io = mapscript::msIO_installStdoutToBuffer();
  $dispatch = $map->OWSDispatch($req);

  # at this point, the client's request is sent

  # pull out the HTTP headers
  my $ct = mapscript::msIO_stripStdoutBufferContentType();

  # and then pick up the actual content of the response
  my $content = mapscript::msIO_getStdoutBufferString();

  my $xml  = XML::LibXML->new();
  my $xslt = XML::LibXSLT->new();

  # load XML content
  my $source = $xml->parse_string($content);

  # load XSLT document
  my $style_doc = $xml->parse_file($xsltfile);
  my $stylesheet = $xslt->parse_stylesheet($style_doc);

  # invoke the XSLT transformation
  my $results = $stylesheet->transform($source);
  # print out the result (header + content)
  print "Content-type: $ct\n\n";
  print $stylesheet->output_string($results);
}

# else process as normal
else {
  $dispatch = $map->OWSDispatch($req);
}

Java Example

One benefit of redirection of output to a buffer is that it is thread-safe. Several threads in the same process can be actively processing requests and writing their results to distinct output buffers. This Java example, used to test multi-threaded access demonstrates that.

import edu.umn.gis.mapscript.mapObj;
import edu.umn.gis.mapscript.OWSRequest;
import edu.umn.gis.mapscript.mapscript;

class WxSTest_thread extends Thread {

  public String       mapName;
  public byte[]       resultBytes;

  public void run() {
      mapObj map = new mapObj(mapName);

      map.setMetaData( "ows_onlineresource", "http://dummy.org/" );

      OWSRequest req = new OWSRequest();

      req.setParameter( "SERVICE", "WMS" );
      req.setParameter( "VERSION", "1.1.0" );
      req.setParameter( "REQUEST", "GetCapabilities" );

      mapscript.msIO_installStdoutToBuffer();

      int owsResult = map.OWSDispatch( req );

      if( owsResult != 0 )
          System.out.println( "OWSDispatch Result (expect 0): " + owsResult );

      resultBytes = mapscript.msIO_getStdoutBufferBytes();
  }
}

public class WxSTest {
  public static void main(String[] args)  {
      try {
          WxSTest_thread tt[] = new WxSTest_thread[100];
          int i;
          int expectedLength=0, success = 0, failure=0;

          for( i = 0; i < tt.length; i++ )
          {
              tt[i] = new WxSTest_thread();
              tt[i].mapName = args[0];
          }

          for( i = 0; i < tt.length; i++ )
              tt[i].start();

          for( i = 0; i < tt.length; i++ )
          {
              tt[i].join();
              if( i == 0 )
              {
                  expectedLength = tt[i].resultBytes.length;
                  System.out.println( "Document Length: " + expectedLength + ", expecting somewhere around 10000 or more." );
              }
              else if( expectedLength != tt[i].resultBytes.length )
              {
                  System.out.println( "Document Length:" + tt[i].resultBytes.length + " Expected:" + expectedLength );
                  failure++;
              }
              else
                  success++;
          }

          System.out.println( "Successes: " + success );
          System.out.println( "Failures: " + failure );

      } catch( Exception e ) {
          e.printStackTrace();
      }
  }
}

PHP Example

Most of the same capabilities are accessible in php mapscript. Here is an example displaying a wms capabilities.

Example1 : get the capabilities
This is for example what a url could look like :
http://.../php/ows.php?service=WMS&version=1.1.1&Request=GetCapabilities


<?php

dl("php_mapscript_4.10.0.dll");

$request = ms_newowsrequestobj();

$request->loadparams();

/*exampple on how to modify the parameters :
 forcing the version from 1.1.1 to 1.1.0 */
$request->setParameter("VeRsIoN","1.1.0");

ms_ioinstallstdouttobuffer();

$oMap = ms_newMapobj("../../service/wms.map");

$oMap->owsdispatch($request);

$contenttype = ms_iostripstdoutbuffercontenttype();

$buffer = ms_iogetstdoutbufferstring();

header('Content-type: application/xml');
echo $buffer;

ms_ioresethandlers();

?>


Example2 : get the map
This is for example what a url could look like :

http://.../php/ows.php?SERVICE=WMS&VeRsIoN=1.1.1&Request=GetMap&
LAYERS=WorldGen_Outline


<?php

dl("php_mapscript_4.10.0.dll");

$request = ms_newowsrequestobj();

$request->loadparams();

ms_ioinstallstdouttobuffer();

$oMap = ms_newMapobj("../../service/wms.map");

$oMap->owsdispatch($request);

$contenttype = ms_iostripstdoutbuffercontenttype();

if ($contenttype == 'image/png')
   header('Content-type: image/png');

ms_iogetStdoutBufferBytes();

ms_ioresethandlers();

?>

Use in Non-CGI Environments (mod_php, etc)

The loadParams() call establish parses the cgi environment varabiables (QUERY_STRING, and REQUEST_METHOD) into parameters in the OWSRequest object. In non-cgi environments, such as when php, python and perl are used as « loaded modules » in Apache, or Java with Tomcat, the loadParams() call will not work - in fact in 4.10.x it will terminate the web server instance.

It is necessary in these circumstances for the calling script/application to parse the request url into keyword/value pairs and assign to the OWSRequest object by other means, as shown in some of the above examples explicitly setting the request parameters.

Post Processing Capabilities

In the following python example, we process any incoming WxS request, but if it is a GetCapabilities request we replace the Service section in the capabilities with a section read from a file, that is carefully tailored the way we want.

#!/usr/bin/env python

import sys
import elementtree.ElementTree as ET

import mapscript

req = mapscript.OWSRequest()
req.loadParams()

map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )

#
# Handle anything but a GetCapabilities call normally.
#
if req.getValueByName('REQUEST') <> 'GetCapabilities':

  map.OWSDispatch( req )

#
# Do special processing for GetCapabilities
#
else:
  mapscript.msIO_installStdoutToBuffer()

  map.OWSDispatch( req )

  ct = mapscript.msIO_stripStdoutBufferContentType()
  content = mapscript.msIO_getStdoutBufferString()
  mapscript.msIO_resetHandlers()

  # Parse the capabilities.

  tree = ET.fromstring(content)

  # Strip out ordinary Service section, and replace from custom file.

  tree.remove(tree.find('Service'))
  tree.insert(0,ET.parse('./Service.xml').getroot())

  # Stream out adjusted capabilities.

  print 'Content-type: ' + ct
  print
  print ET.tostring(tree)