Make an OpenLayers Preview from Metadata

My horrific Metadata Navigator hack garnered a lot more interest than I expected, so in addition to patching a couple of bugs some users pointed out (thanks folks!), in the 1.1 release I added an interactive map preview using the onlink metadata element. If your metadata has a WMS link built in, your user will get an interactive map they use to view and identify features. You can of course download and run the latest release, but I’d thought I’d go over what I did here. Big hat tip to GeoServer’s map preview implementation for some of the code.

I decided the default summary page was the most logical place to drop the interactive map if it’s available. So in the summary XSL document, I dropped in this:

<div id="map"></div>
<div id="nodelist">
<em>Click on the map to get feature info</em>
</div>
<script type="text/javascript">
wmsurl = '<xsl:value-of
select="metadata/idinfo/citation/citeinfo/onlink"
disable-output-escaping="yes"/>' ;
map_init(wmsurl);
</script>

Basically we’re setting up a couple of div’s on the page to hold the map and the results table. Then we grab the value of the onlink element (empty of it doesn’t exist) and pass it to a map_init() function back in the main page.

We need to do a couple of things at this point. First, we need to make sure what we got was a valid WMS URL. In the old days ESRI would stick your SDE connection info here. I’m a little lazy on this one - I basically check to see if it’s a valid URL and if it has WMS in it - but it should do the trick.

/**
 * Validate the URL is a URL and contains WMS
 */
function isValidWMSURL(s) {
    var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
    if (regexp.test(s) && s.toUpperCase().match('WMS')) {
        return true;
    }
    else {
        return false;
    }
}

Next we’re going to need a function to pull arguments out of the WMS URL and pass them to OpenLayers. Something like this would do the trick:

/**
 * Get URL args
 */
function gup( name, url )
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( url.toLowerCase() );
  if( results == null )
    return "";
  else
    return results[1];
}

So our map_init function, called from our summary XSL, will hit both of those functions and pass the request onto OpenLayers. If it doesn’t find a valid URL, it will hide the DIV’s.

function map_init(wmsurl){
    if (isValidWMSURL(wmsurl)) {
        $("#map").show();
        $("#nodelist").show();
        // Set variables from URL argumemts
        wmsurl = urldecode(wmsurl);
        //main url
        wmsbaseurl = wmsbaseurl = wmsurl.slice(0, wmsurl.indexOf("?"));
        //layername
        layername = gup('layers', wmsurl);
        //bounding box
        bbox = gup("bbox", wmsurl).split(",");
        // projection
        epsg = gup("srs", wmsurl);
        // style
        sld = gup("style", wmsurl);
        // call mapgen
        mapgen(wmsbaseurl,layername,bbox, epsg, sld);

    }
    else {
        $("#map").hide();
        $("#nodelist").hide();
    }
}

Now for the meat and potatoes. We’re going to initialize OpenLayers with arguments for the base WMS URL, the layer name, bounding box, projection, and style sheet. We also register the click event to identify features. Note I made a simple PHP proxy to avoid a XSS-up-yours from the browser.

function mapgen(wmsurl, layername, bbox, epsg, sld) {

    // if this is just a coverage or a group of them, disable a few items,
    // and default to jpeg format
    format = 'image/png';

    var bounds = new OpenLayers.Bounds(
        parseFloat(bbox[0]), parseFloat(bbox[1]),
        parseFloat(bbox[2]), parseFloat(bbox[3])
    );
    var options = {
        controls: [],
        maxExtent: bounds,
        maxResolution: 882.7348803710936,
        projection: epsg,
        units: 'm'
    };
    map = new OpenLayers.Map('map', options);

    // setup single tiled layer
    untiled = new OpenLayers.Layer.WMS(
        layername, wmsurl,
        {
            srs: epsg,
            layers: layername,
            styles: sld,
            format: format
        },
        {singleTile: true, ratio: 1}
    );

    map.addLayers([untiled]);

    // build up all controls
    map.addControl(new OpenLayers.Control.PanZoom());
    map.addControl(new OpenLayers.Control.Navigation());
    map.addControl(new OpenLayers.Control.Scale());
    map.addControl(new OpenLayers.Control.MousePosition());

    map.zoomToExtent(bounds);

    // support GetFeatureInfo
    map.events.register('click', map, function (e) {
        document.getElementById('nodelist').innerHTML = "Loading... please wait...";
        var params = {
            REQUEST: "GetFeatureInfo",
            EXCEPTIONS: "application/vnd.ogc.se_xml",
            BBOX: map.getExtent().toBBOX(),
            X: e.xy.x,
            Y: e.xy.y,
            INFO_FORMAT: 'text/html',
            QUERY_LAYERS: map.layers[0].params.LAYERS,
            FEATURE_COUNT: 50,
            Srs: epsg,
            Layers: layername,
            Styles: '',
            WIDTH: map.size.w,
            HEIGHT: map.size.h,
            format: format};
        $.get("services/ws_wms_query_proxy.php", params, function(data) {
            $("#nodelist").html(data);
            });
        OpenLayers.Event.stop(e);
    });
}

That’s it in a nutshell. Thanks to solid open standards like WMS and the fantastic OpenLayers library it’s a piece of cake.

mn

You can see it working on our running Metadata Navigator. Just click on Jurisdictions under Latest Updates on the left. And of course, feel free to download the code on our Projects page.