Author:Rodrigo Martín LÓPEZ GREGORIO
Contact:rodrigomlg (at) gmail (dot) com
Last Updated:2007/06/14

Table of Contents


Introduction

msCross is a great Javascript interface that provide a web interface to mapserver with basic functions like Zoom or Pan (and other advanced like wms support). The problem that many mapserver users confront first time they try to use this interface is that they normally started with PHP MapScript and then they dont know how to connect their PHP-MapScript files to msCross interface. Aditionally, PHP MapScript allows mapserver users to create layers or add features dinamically wich could not be achieved with the use of just msCross. So I think that using msCross as client interface and letting PHP MapScript do it job at server side most users can get great results.
You can get the last version of this library and see all the features it provides in the official page. I have nothing to do with the Datacrossing people (wich I think have made a great work); I'm just a mscross very satisfied user.
I wrote this text as response to a question in the mapserver users mailing list and bearing in mind that english is not my mother tongue it can be writed not as well as I want. However I was encouraged by a list member to publish it so here it is.


First considerations...

First of all, if you play around a little bit with the mscross code, you will see that the way mscross obtains the image of the map is asking directly to mapserver cgi interface, generating the necesary url and assigning it to an <img> HTML element (created dinamically by the javascript code). So, what ajax interface was expecting from the server is an image file and not an HTML file. That is why you must modify your PHP MapScript file to return an image file instead of just HTML code.


Starting...

So, lets go with a simple example... To make it simple, supose you have a mapfile ready to use with mapserver and do not need to create anything dinamically so the PHP MapScript becomes quite simple. Lets supose the mapfile have a png OUTPUTFORMAT, and three LAYERS named "countrys" "rivers" and "sea". If the mapfile is located in /mymaps/ and it name is " example1.map", then the CGI url to obtain a map (the one generated by mscross) is something like this:

http://myserver/cgi-bin/mapserv.exe?map=/mymaps/example1.map&mode=map (continues below)
&mapext=5649512+6099485+5711778+6144474&mapsize=800+600&layers=countrys%20rivers%20sea

(note that I'm using mapserver for windows but on linux the url will be almost the same)

The "object" returned by mapserver CGI interface will be an Image object ( i.e. the content of the image).

So the first thing you need to use your PHPMapScript with the mscross interface is make that your PHP MapScript file return an image object and not a html object with an <img> element on it. So, lets see the contest of this simple PHP MapScript file:

<?php
header("Content-type: image/png");
//Before we send any data to the browser we set the header "Content-type"
//of the response to "image/png" so the browser interprets the content 
//of the response as a png image file.

$map = ms_newMapObj("/mymaps/example1.map");
// We create the map object based on the example1.map file as template

$map->setSize(800,600);
// and set the image size (resolution)
// Update: The map size must be setted before the extent, otherwise the extent 
// will be adjusted to the aspect ratio of the map defined on SIZE parameter
// of MAP object in your mapfile

$map->setExtent(5649512,6099485,5711778,6144474);
// We set the extent of map

$map->getLayerByName("countrys")->set(status,MS_ON);
$map->getLayerByName("rivers")->set(status,MS_ON);
$map->getLayerByName("sea")->set(status,MS_ON);
// Set the status of three layers to on

$image=$map->draw();
$imagename=$image->saveWebImage();
//Draw the image to a temp file and copy image name to the $imagename variable

$image = ImageCreateFromPng("/ms4w/tmp/".$imagename);
// Read the image saved previously to the $image variable

imagePng($image);
// Return the image content to the browser
?>

Maybe there is a way to avoid the save to disk and read from disk operation but I didn't waste too much time looking for an alternative. So the previous PHP MapScript will create a mapobject, set the properties properly and then return the image to the browser just like an image object.

If you call this php file from your browser, the browser will get a png image file with the map drawed. Obviously the PHP MapScript could be more complex, create layers, class and labels dinamically, but it must be always return an image file which is achieved with the header function in conjunct with the imagePng call at the end of the MapScript.


Going further...

The next thing to do is make this MapScript more flexible, so it could recieve the map extent, map size and layers (and maybe the mapfile even) as parameters. Sinse mscross use the GET method to obtain images we can modify our MapScript file to get this parameters and set it properly. So, if we mantain the variables name as the ones used to get images from CGI interface to make it more simple we can modify our PHP MapScript file so it can be more flexible and modify its output according to the url parameters:

<?php
header("Content-type: image/png");

$map = ms_newMapObj($_GET['map']);
// We create the map object based on the mapfile received as parameter

$size = explode(" ",$_GET['mapsize']);
$map->setSize($size[0], $size[1]);
// and set the image size (resolution) based on mapsize parameter
// Update: The map size must be setted before the extent, otherwise the extent 
// will be adjusted to the aspect ratio of the map defined on SIZE parameter
// of MAP object in your mapfile

$extent = explode(" ",$_GET['mapext']);
$map->setExtent($extent[0], $extent[1], $extent[2], $extent[3]);
// We get the mapext parameter... split it on its 4 parts using 
// the space character as splitter

$layerslist=$_GET['layers'];
for ($layer = 0; $layer < $map->numlayers; $layer++) {
    $lay = $map->getLayer($layer);
    if ((strpos($layerslist,($map->getLayer($layer)->name)) !== false) 
        or (($map->getLayer($layer)->group != "") and 
        (strpos($layerslist,($map->getLayer($layer)->group)) !== false))){
// if the name property of actual $lay object is in $layerslist
// or the group property is in $layerslist then the layer was requested
//so we set the status ON... otherwise we set the stat to OFF
        $lay->set(status,MS_ON);
    } else {
        $lay->set(status,MS_OFF);
    }
}

// The next lines are the same as previous mapscript
$image=$map->draw();
$imagename=$image->saveWebImage();
$image = ImageCreateFromPng("/ms4w/tmp/".$imagename);
imagePng($image);
?>

If you want to get a map drawed throw this mapscript, you must now pass various parameters in the url... something like this:

http://myserver/myPHPMapScript.php?map=/mymaps/example1.map (continues below)
&mapext=5649512+6099485+5711778+6144474&mapsize=800+600 (continues below)
&layers=countrys%20rivers%20sea

Again, the MapScript could be more complex but always must return an image object.


Making it work with msCross...

So the last thing we must do is get it work with the mscross ajax... Lets work with the last version of mscross javascript available here

First take a look at the get_map_url() method of mscross class on mscross-1.1.8.js file... It looks something like this (I remove some lines which gives support to use wms servers):

this.get_map_url = function()
{
  var my_url;

  if (_protocol == 'mapservercgi')
  {
    var size = 'mapsize=' + (_map_w+_map_w_bord+_map_w_bord) + '+'
                          + (_map_h+_map_h_bord+_map_h_bord);
    var ext  = 'mapext=' + (_ext_Xmin-i.wPixel2real(_map_w_bord)) + '+'
                         + (_ext_Ymin-i.hPixel2real(_map_h_bord)) + '+'
                         + (_ext_Xmax+i.wPixel2real(_map_w_bord)) + '+'
                         + (_ext_Ymax+i.hPixel2real(_map_h_bord)) ;

    my_url = _cgi + '?mode=' + _mode + '&' + _map_file + '&' +
             ext + '&' + size + '&layers=' + _layers;

    // Opera9 Bug Fix (onload event don't work if image is in cache)
    if (browser.isOP ) {my_url = my_url + '&' + Math.random();}
  }

  return my_url + '&' + _args;
}

Basically, this function build the url of the MapServer CGI interface, so we must get it work with our recently created PHP MapScript file. If you take a look at the line in bold, there is where the url is created:

my_url = _cgi + '?mode=' + _mode + '&' + _map_file + '&' + ext + '&' + size + '&layers=' + _layers;

there are some fixed (strings) parts and some variable parts in this line... first it put the _cgi content which is set using the mscross setCgi() method. Then it add the mode parameter that we dont need to use so it could be removed. Then the mapfile parameter which is set using the mscross setMapFile() method. After mapfile parameter it puts the extent parameter at ext variable which is created some lines before and is based on the mscross object properties setted by the setFullExtent() and setExtent() methods and the size parameter (width and height of image) which are calculated on the mscross object creation from container div width and height. Finaly it pass the layers parameter based on _layers variable setted by the mscross setLayers() method.

So we want to get this url look like the one we create for the second PHP MapScript file, so we need to set the variables properly to get my_url look like:

http://myserver/myPHPMapScript.php?map=/mymaps/example1.map (continues below)
&mapext=5649512+6099485+5711778+6144474&mapsize=800+600 (continues below)
&layers=countrys%20rivers%20sea

The way to obtain this is:

set the _cgi variable to "http://myserver/myPHPMapScript.php" using the setCgi() method.
set the mapfile to "/mymaps/example1.map" using the setMapFile() method.
set map extention to (5649512,6099485,5711778,6144474) using the setFullExtent() method.
and set the layers to "countrys rivers sea" using the setLayers() method
(the map size is setted automatically when the mscross object is created).
and finally if yo want to avoid the pass of mode parameter you can modify the get_map_url() method and remove it from the line as follows:

my_url = _cgi + '?' + _map_file + '&' + ext + '&' + size + '&layers=' + _layers;

So, at this point we can make a simple html file to get our mscross and PHP MapScript working together... a simple html could be the next one:

<html>
<script src="mscross-1.1.8.js" type="text/javascript"></script>
<body>
<div id="map_tag" style="position:absolute; 
    top:10px; left:10px; width: 800px; height: 600px; border-width:1px;
    border-color:#000088; border-style:solid;"></div>
</body>
<script type="text/javascript">
myMap = new msMap(document.getElementById('map_tag'),' ');
myMap.setCgi( 'http://myserver/myPHPMapScript.php ' );
myMap.setFullExtent( 5649512,5711778,6099485);
myMap.setMapFile( '/mymaps/example1.map' );
myMap.setLayers('countrys rivers sea');
myMap.redraw();
</script>
</html>

Ok, I think that this is all :P. I hope I was clear enough but I encourage you to ask me if you have any doubt.