<?php
define('idDiGIR_wms','$Id: DiGIR_wms.php,v 1.4 2003/07/16 13:00:50 rdg Exp $');
if (defined('idDIGIR_VERSION_REQUEST'))
    return;

define('DIGIR_REQUEST',true);
/**
* Implements a simple OGC WMS service for specimen data records accessible through
* a DiGIR content provider.
*
* See @link http://chipotle.speciesanalyst.net/digir/ow.asp?DiGIRWMS
*
* Note that it is *rather* helpful to index the lat and lon fields
* in your source table!
*
* Example useage: @link http://velo.dyndns.info/phpdev/digir/DiGIR_wms.php?REQUEST=GETMAP&SRS=EPSG:4326
* &format=image/png&WIDTH=800&HEIGHT=400&TRANSPARENT=FALSE&LAYERS=example
* &BBOX=-180,-90,180,90&BGCOLOR=0xFFFF99&filter=@attr+1=1+%22otus%25%22
*
*
* @author Dave Vieglais <vieglais@ku.edu>
* @version:  $Id: DiGIR_wms.php,v 1.4 2003/07/16 13:00:50 rdg Exp $
* Dependencies:
*     GD php graphics library
*     phpXPath (v3.2, http://sourceforge.net/projects/phpxpath )
*     DiGIR routines
*
* <h3>License</h3>
* <p>Copyright (c) 2002 The University of Kansas Natural History Museum and
* Biodiversity Research Center.  All rights reserved.</p>
*
* <p>Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* with 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:</p>
*
* <dl><dd>Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimers.</dd>
* <dd>Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.</dd>
* <dd>Neither the names of The University of Kansas Natural History Museum and
* Biodiversity Research Center, The University of Kansas at Lawrence, nor the
* names of its contributors may be used to endorse or promote products derived
* from this Software without specific prior written permission.</dd></dl>
*
* <p>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
* CONTRIBUTORS 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.</p>
*
*/

$ScriptCalledDirect = ((isset($_SERVER['SCRIPT_NAME']) &&
                        preg_match("/.\/DiGIR_wms.php/i",$_SERVER['SCRIPT_NAME'])) ||
                       (isset($_SERVER['argv'][0]) &&
                        strcasecmp($_SERVER['argv'][0],'DiGIR_wms.php') === 0));

if (!$ScriptCalledDirect)
{
    //The script was included.  Do not continue.
    //The defines made above will still be valid.
    return;
}

/**
*
*
*/
define("g_DEFAULTSRS","EPSG:4326");
define("g_DEFAULTFORMAT","image/png");
define("g_DEFAULTWIDTH",400);
define("g_DEFAULTHEIGHT",200);
define("g_DEFAULTBOUNDS","-180,-90,180,90");
define("g_BGCOLOR","0x000000");
define("g_FGCOLOR","0xFF0000");

define("g_DEBUGMODE",0);

//error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_CORE_WARNING & ~E_COMPILE_WARNING);

//the output format of exceptions
//use "application/vnd.ogc.se_xml" or "application/vnd.ogc.se_inimage"
$g_exceptionFormat = "application/vnd.ogc.se_xml";

//set the level PHP of error reporting
error_reporting(E_ALL
                & ~E_NOTICE
                & ~E_WARNING
                & ~E_CORE_WARNING
                & ~E_COMPILE_WARNING);

define('PHP_SELF',$_SERVER['PHP_SELF']);

//what OS?
if (substr(PHP_OS, 0, 3) == 'WIN')
{
    define('DIGIR_OS_WINDOWS', true);
    define('DIGIR_OS_UNIX',    false);
    define('DIGIR_OS',    'Windows');
    define('PATHSEP',';');
}
else
{
    define('DIGIR_OS_WINDOWS', false);
    define('DIGIR_OS_UNIX',    true);
    define('DIGIR_OS',    'Unix'); // blatant assumption
    define('PATHSEP',':');
}

//force PHP to use only local includes - should all be part of DiGIR as of
//version 1.3 (20 Sep 2002)
ini_set('include_path','.'.PATHSEP.'pear');

@include_once("localConfig.php");
require_once("DiGIR_globals.php");
require_once("DiGIR_utils.php");

define("_DEBUG",(bool) getVar("debug",false));

//make sure adodb is on your include path or modify this
//entry so php can find it!
require_once("adodb/adodb.inc.php");
require_once("DiGIR_flt.php");
require_once("DiGIR_config.php");
require_once("xpath/XPath.class.php");


class pointStyleX
{
	var $width = 5;
	var $height = 5;
	var $fg = 0;
	var $img = null;
	
	function pointStyleX($width=5,$height=5,$color="255,0,0")
	{
		//echo "w=$width,h=$height,color=$color\n";
		$this->width = $width;
		$this->height = $height;
		$rgb = explode(",",$color);
		$this->img = ImageCreate($this->width,$this->height);
		$bg = ImagecolorAllocate($this->img,0,0,0);
		$this->fg = ImageColorAllocate($this->img,$rgb[0],$rgb[1],$rgb[2]);
		ImageColorTransparent($this->img,$bg);
		//echo "img handle = ".$this->img."\n";
		//echo "fg handle = ".$this->fg."\n";
	}
	
	function make()
	{
		ImageLine($this->img,0,0,$this->width,$this->height,$this->fg);
		ImageLine($this->img,0,$this->height,$this->width,0,$this->fg);
	}
	
	function destroy()
	{
		ImageDestroy($this->img);
	}
}

class pointStylePlus extends pointStyleX
{
	function make()
	{
		$cx = $this->width / 2;
		$cy = $this->height /2;
		//echo "with = ".$this->width."height=".$this->height."cx = $cx, cy = $cy\n";
		ImageLine($this->img,0,$cy,$this->width,$cy,$this->fg);		
		ImageLine($this->img,$cx,0,$cx,$this->height,$this->fg);		
	}
}

class pointStyleFilledCircle extends pointStyleX
{
	function make()
	{
		$cx = $this->width / 2;
		$cy = $this->height /2;
		ImageFilledEllipse($this->img,$cx,$cy,$this->width,$this->height,$this->fg);
	}
}

class pointStyleCircle extends pointStyleX
{
	function make()
	{
		$cx = $this->width / 2;
		$cy = $this->height /2;
		ImageArc($this->img,$cx,$cy,$this->width,$this->height,0,360,$this->fg);
	}
}

/**
* simpleWMSLayer
*
* Implements a simple WMSLayer
*
*/
class simpleWMSLayer
{
  var $m_params;
  var $m_xscale;
  var $m_yscale;
  var $m_surface;
  var $m_colors;
  var $m_ptSize;
  var $marker;

/**
* Create the layer and initialize the colors etc
* @param array $params Parameters loaded from HTTP request
*/
function simpleWMSLayer($params)
{
	$this->m_params = $params;
	$this->m_xscale = $params["width"]/($params["bbox"][2]-$params["bbox"][0]);
	$this->m_yscale = $params["height"]/($params["bbox"][3]-$params["bbox"][1]);
	$this->m_surface = NULL;
	
	$this->m_surface = ImageCreate($params["width"],$params["height"]);
	$this->m_colors[0] = ImageColorAllocate($this->m_surface,
        $this->m_params["bgcolor"][0],
        $this->m_params["bgcolor"][1],
        $this->m_params["bgcolor"][2]);
        
	$this->m_colors[1] = ImageColorAllocate($this->m_surface,
        $this->m_params["fgcolor"][0],
        $this->m_params["fgcolor"][1],
        $this->m_params["fgcolor"][2]);
    
    $this->marker = null;    
	if ($params["markertype"] == "cross")
	{
		$s = implode(",",$params["fgcolor"]);
		$this->marker = new pointStyleX($params["markerwidth"],$params["markerheight"],$s);
		$this->marker->make();
	}
	elseif ($params["markertype"] == "plus")
	{
		$s = implode(",",$params["fgcolor"]);
		$this->marker = new pointStylePlus($params["markerwidth"],$params["markerheight"],$s);
		$this->marker->make();
	}
	elseif ($params["markertype"] == "circle")
	{
		$s = implode(",",$params["fgcolor"]);
		$this->marker = new pointStyleCircle($params["markerwidth"],$params["markerheight"],$s);
		$this->marker->make();
	}
	elseif ($params["markertype"] == "fcircle")
	{
		$s = implode(",",$params["fgcolor"]);
		$this->marker = new pointStyleFilledCircle($params["markerwidth"],$params["markerheight"],$s);
		$this->marker->make();
	}
        
	if ($this->m_params["transparent"] > 0)
		ImageColorTransparent($this->m_surface,$this->m_colors[0]);
	
	$this->m_ptSize = 2;
}

/**
* Returns the image to the client.  This must be the last of the content
* output by the script.
*/
function send()
{
    header("Content-type: image/png");
	ImagePng($this->m_surface);
	ImageDestroy($this->m_surface);
	if ($this->marker)
		$this->marker->destroy();
}

/**
* Converts latitude and longitude to pixel coordinates by scaling to
* the image dimensions.
* @param float $x Longitude of point to be converted
* @param float $y Latitude of point to be converted
* @return array 0 = x-coordinate of pixel, 1 = y-coordinate of pixel
*/
function wc2ic($x,$y)
{
	$res = array();
	$res[0] = ($x-$this->m_params["bbox"][0])*$this->m_xscale;
	$res[1] = $this->m_params["height"]-
        ($y-$this->m_params["bbox"][1])*$this->m_yscale;
	return $res;
}

/**
* Draws a single point on the image.  This one generates a cross, which is a
* lot faster than the ellipse.  A better option might be to pre-create small
* rasters as the required symbols then bitblt them.
*/
function drawPt($x,$y)
{
	$ic = $this->wc2ic($x,$y);
	if ($this->marker != null)
	{
		$dx = $ic[0] - ($this->marker->width / 2);
		$dy = $ic[1] - ($this->marker->height / 2);
//		echo "dx = $dx, dy = $dy\n";
		ImageCopy($this->m_surface,$this->marker->img,$dx,$dy,0,0,$this->marker->width,$this->marker->height);
	}
	else
	{
		$x0 = $ic[0] - $this->m_ptSize;
		$y0 = $ic[1] - $this->m_ptSize;
		$x1 = $ic[0] + $this->m_ptSize;
		$y1 = $ic[1] + $this->m_ptSize;
		ImageLine($this->m_surface,$x0,$ic[1],$x1,$ic[1],$this->m_colors[1]);
		ImageLine($this->m_surface,$ic[0],$y0,$ic[0],$y1,$this->m_colors[1]);
	}
}


/**
* Renders the result set RS. xfld and yfld are the indexes to the
* longitude and latitude respectively.
*/
function render(&$RS,$xfld,$yfld)
{
	while (!$RS->EOF)
	{
		$x = $RS->fields[$xfld];
		$y = $RS->fields[$yfld];
		$this->drawPt($x,$y);
		$RS->MoveNext();
	}
}

}  // -- simpleWMS --
//////////////////////////////////////////////////////////////////////////

/**
* Generates a WMS error
*/
function sendError($msg)
{
	if (g_DEBUGMODE)
		return;
	global $g_exceptionFormat;
	if (strcasecmp("application/vnd.ogc.se_xml",$g_exceptionFormat) == 0)
	{
		// xml output of exception
		header("Content-type: ".$g_exceptionFormat);
		$s = "<xml verison='1.0'>";
		$s .= "<ServiceExceptionReport>\n";
		$s .= "<ServiceException>";
		$s .= HTMLEntities($msg);
		$s .= "</ServiceException>";
		$s .= "</ServiceExceptionReport>\n";
	}
	echo($s);
	die();
}


/**
* Loads WMS operational parameters from GET or POST variable
* 
* Styles defiend as: Color+type+width+height
* Color = HEX for color
* Type = "cross" | "plus" | "circle"
* width = pixel width
* height = pixel height
*/
function loadWMSParams()
{
	$res = array();
	$res["version"] = getVar("version","");
	$res["request"] = strtolower(getVar("request","getmap"));
	$layers = explode(",",strtolower(getVar("layers","example")));
	if (count($layers) <= 0)
		sendError("No layers were specified in request.");
	$res["layers"] = $layers[0];
    $styles = explode(",",strtolower(getVar("styles",g_FGCOLOR)));
	//if (count($res["layers"]) != count($res["styles"]))
	//	sendError("Style count must match layer count.");
	if (strlen($styles[0]) > 5)
	{
		$st = explode(" ",$styles[0]);
        $r = hexdec(substr($st[0], 2, 2));
        $g = hexdec(substr($st[0], 4, 2));
        $b = hexdec(substr($st[0], 6, 2));
        $res["fgcolor"] = array($r,$g,$b);
        $res["markertype"] = $st[1];
        $res["markerwidth"] = $st[2];
        $res["markerheight"] = $st[3];
    }
    $res["styles"] = $styles[0];	
	if (g_DEBUGMODE)
		$res["srs"] = getVar("srs",g_DEFAULTSRS);
	else
		$res["srs"] = getVar("srs","");
	if (strcasecmp($res["srs"],g_DEFAULTSRS) != 0)
		sendError("Service does not support requested SRS: ".$res["srs"]);
	if (g_DEBUGMODE)
		$res["format"] = getVar("format",g_DEFAULTFORMAT);
	else
		$res["format"] = getVar("format","");
//this is breaking the rules of WMS to comment this out...
//	if (strcasecmp($res["format"],g_DEFAULTFORMAT) != 0)
//		sendError("Unsupported or undeclared image format: ".$res["format"]);
    $res["format"] = g_DEFAULTFORMAT;
	$res["width"] = getVar("width",g_DEFAULTWIDTH);
	$res["height"] = getVar("height",g_DEFAULTHEIGHT);
	$res["bbox"]  = explode(",",getVar("bbox",g_DEFAULTBOUNDS));
	if (count($res["bbox"]) < 4)
		sendError("Expecting four values in BBOX parameter:".getVar("bbox",""));
	$res["transparent"] = getVar("transparent",0);
	if (!is_int($res["transparent"]))
	{
		if (strcasecmp($res["transparent"],"true") == 0)
			$res["transparent"] = 1;
		else
			$res["transparent"] = 0;
	}
	$color = getVar("bgcolor",g_BGCOLOR);
	$r = hexdec(substr($color, 2, 2));
	$g = hexdec(substr($color, 4, 2));
	$b = hexdec(substr($color, 6, 2));
	$res["bgcolor"] = array($r,$g,$b);
	
	// the filter can be either PQN using Darwin Core attributes or
	// a DiGIR filter.
	$res["filter"] = trim(stripcslashes(getVar("filter",'')));
	return $res;
}

/**
* Simple SQL for getting coords within the specified range
*/
function sqlBBOX($x0,$y0,$x1,$y1)
{
	$sql = " (latitude IS NOT NULL) AND (longitude IS NOT NULL)";
	$sql .= " AND ((latitude BETWEEN ".$y0." AND ".$y1.") AND ";
    $sql .= "(longitude BETWEEN ".$x0." AND ".$x1."))";
	return $sql;
}

/**
* Just dumps the result set.
*/
function dumpRS(&$RS)
{
    echo "<pre>";
	while (!$RS->EOF)
	{
		$x = $RS->fields[0];
		$y = $RS->fields[1];
		echo $x.", ".$y."\n";
		$RS->MoveNext();
	}
    echo "</pre>";
}

/// Main ////////////////////////////////
//$Config is a global variable.  Should really change this.
$Config = NULL;

//Load the WMS parameters
$params = loadWMSParams();

//echo "<pre>";
//print_r($params);
	
if (strcasecmp($params['request'],"getmap") == 0)
{
    //request is to return a map image

    //Load the GD library
    if (!loadlibrary_x('gd2'))
    {
      sendError("PHP extension 'gd2' could not be loaded.");
    }

    $wms = new simpleWMSLayer($params);

    $params["resourceConfigFile"] = getResourceConfigFile($params["layers"]);

    if ($params["resourceConfigFile"] != NULL)
    {
        //create a filter and parse the filter string
        $flt = new filter();
        
        $flt->parse($params['filter']);

        //load the actual configuration information
        $cfgBuilder = new ConfigBuilder($params["resourceConfigFile"]);
        $Config = $cfgBuilder->Config;

        //create a SQL statement to retrieve the specified points
        $sqlSELECT = "SELECT ".$Config->WMSSELECTfields();
        $sqlFROM = $Config->FROMclause();
        $sqlWHERE = $flt->toSQL($Config);
        
        if (strlen($sqlWHERE) > 1)
        {
            $sqlWHERE .= " AND ";
        }
        $sqlWHERE = " WHERE ".$sqlWHERE.sqlBBOX($params["bbox"][0],$params["bbox"][1],$params["bbox"][2],$params["bbox"][3]);
        $sql = $sqlSELECT.$sqlFROM.$sqlWHERE;

        //echo "<pre>".$sql."</pre>";

        //connect to the database and retrieve the result set
        $cn = &ADONewConnection($Config->connectionType);
        $cn->PConnect($Config->connectionString,$Config->connectionUID,$Config->connectionPWD,$Config->connectionDB);
        $rs = &$cn->Execute($sql);

        //render the results to the surface and send to the client
        $wms->render($rs,1,0);
        $wms->send();
        die();
    }
    else
    {
        sendError("Specified layer is not available (".$params['layers'].").");
    }
}
elseif (strcasecmp($params['request'],"getfeatureinfo") == 0)
{
    // return information about a feature
    /*
    get coordinate
    convert to latitude and longitude from pixel coords
    query database for matching coord
    return GML info about the feature(s)
    */
    sendError("Sorry - getFeatureInfo is not implemented yet.");
}
else
{
    // return metadata  (assume getcapabilities)
    header("Content-type: text/xml");
   	$fp = fopen(DIGIR_CONFIG_DIR."wmsCapabilities.xml","r");
   	if (!$fp)
   	{
   	    sendError("Problem reading metadata");
   	}
	while (!feof($fp))
	{
		$s = fgets($fp,4096);
		echo $s;
		flush();
	}
	fclose($fp);
	die();
}
?>
