Location: PHPKode > projects > PhpArcIMS > phpConnector/phparcims_query.php
<?php
/*
*    Copyright 2001, Christoph Spoerri
*
*    This file is part of the phpArcIMS connector.
*
*    phpArcIMS is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    phpArcIMS is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with Foobar; if not, write to the Free Software
*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*/
/*
	CHANGE LOG
	spoerri: 1/12/2003
			added aimsRecordset->getField
	spoerri: 10/04/2003
			change the aimsRecord->findField() to return only 1/0 and not the value
			added the aimsRecord->getValue() method
			added the aimsSpatialQuery->setWhereClause() method
			renamed the aimsSpatialQuery->addSpatialFilter() to setSpatialFilter
	spoerri: 5/08/2002
			added Envelope property to aimsRecordset
			added aimsRecord class
	aje	19/07/2002
		remove aimsRecord
		modify aimsRecordset

*/

/**
* The field represents the row_id of the table.
* @package phparcims
* @constant string FIELD_TYPE_ROWID
*/
DEFINE(FIELD_TYPE_ROWID,-99);
/**
* The field contains shape information.
* @package phparcims
* @constant string FIELD_TYPE_SHAPE
*/
DEFINE(FIELD_TYPE_SHAPE,-98);
/**
* The field contains true or false (single bit).
* @package phparcims
* @constant string FIELD_TYPE_BIT
*/
DEFINE(FIELD_TYPE_BIT,-7);
/**
* The field contains long integer values.
* The values can be between -9223372036854775808 and 9223372036854775807 or 0 and 18446744073709551615.
* @package phparcims
* @constant string FIELD_TYPE_LONG
*/
DEFINE(FIELD_TYPE_LONG,-5);
/**
* The field contains integer values.
* The values can be between -2147483648 and 2147483647 or 0 and 4294967295.
* @package phparcims
* @constant string FIELD_TYPE_INT
*/
DEFINE(FIELD_TYPE_INT,4);
/**
* The field contains short integer values.
* The values can be between -32768 and 32767 or 0 and 65535.
* @package phparcims
* @constant string FIELD_TYPE_SHORT
*/
DEFINE(FIELD_TYPE_SHORT,5);
/**
* The field contains single precision floaing-point numbers.
* The values can be between -3.402823466E+38 and -1.175494351E-38, 0, and between 1.175494351E-38 and 3.402823466E+38.
* @package phparcims
* @constant string FIELD_TYPE_FLOAT
*/
DEFINE(FIELD_TYPE_FLOAT,6);
/**
* The field contains double precision floaing-point numbers.
* The values can be between -1.7976931348623157E+308 and -2.2250738585072014E-308, 0, and between 2.2250738585072014E-308 and 1.7976931348623157E+308.
* @package phparcims
* @constant string FIELD_TYPE_DOUBLE
*/
DEFINE(FIELD_TYPE_DOUBLE,8);
/**
* The field contains characters.
* @package phparcims
* @constant string FIELD_TYPE_CHAR
*/
DEFINE(FIELD_TYPE_CHAR,1);
/**
* The field contains strings of any length.
* @package phparcims
* @constant string FIELD_TYPE_CLOB
*/
DEFINE(FIELD_TYPE_CLOB,12);
/**
* The field contains a date.
* @package phparcims
* @constant string FIELD_TYPE_DATE
*/
DEFINE(FIELD_TYPE_DATE,91);


/**
* The aimsField object can be used to check the definitions of a attribute field
* for an {@link aimsFeatureLayer}. It is part of the {@link aimsRecordset} and can
* be obtained through the (@link aimsRecordset::Fields} list.
* <p><b>Note</b>: all the attributes are 'read-only'. So don't try to change them, it
* will not do you any good ;) </p>
*@brief Field definition object.
*@package phparcims
*@class aimsField
*/
class aimsField{
	/**
	* Name of the field. Is always upper case.
	* @class aimsField
	* @attribute public string Name
	*/
	var $Name;
	/**
	* The type of the field.
	* @class aimsField
	* @attribute public string Type
	*/
	var $Type;
	var $Precision;
	var $Size;

	function aimsField($n,$t,$p='',$s='') {
		$this->Name=strtoupper($n);
		$this->Type=$t;
		if ($p) $this->Precision=$p;
		if ($s) $this->Size=$s;
	}

	function returnTag($parent){
		return '';
		$TagStr='<FIELD name="'.$this->Name.'" ';
		switch ($parent) {
		case 'FIELDS';
			break;
		default:
		}
		return $TagStr.'</FIELD>';
	}
}

/**
* Object can be used to create and access information from a {@link aimsRecordset}.
*
* @class aimsRecord
* @package phparcims
*/
class aimsRecord{
	var $_rs;
	var $_values=array();
	var $Envelope;
	var $Shape;
	var $ShapeType;

	/**
	* Creates an empty record in a recordset.
	* The record will have the same fields as the recordset, but they will be empty. It is adviced
	* NOT to use this function, but instead to use the {@link aimsRecordset::newRecord} call instead.
	* @constructor aimsRecord
	* @param object Recordset The recordset ({@link aimsRecordset}) to which the record is added.
	* @return bool TRUE if record was created. FALSE otherwise.
	*/
	function aimsRecord(&$rs){
		if (!is_a($rs,'aimsRecordset')) return FALSE;
		$this->_rs=&$rs;
		foreach ($rs->_fields as $fld) $this->_values[$fld->Name]='';
		return TRUE;
	}

	/**
	* Add a new field with associated value to the record. This function doesn't add
	* a value to an existing field, but rather 'creates' the field on the fly.
	* @method public addValue
	* @class aimsRecord
	* @param string FieldName The name of the field that's being populated.
	* @param string Value The value to add.
	* @return bool TRUE if value was added. FALSE otherwise.
	*/
	function addValue($fld,$val) {
		if (!array_key_exists($fld,$this->_values)) return FALSE;
		$this->_values[$fld]=$val;
		return TRUE;
	}

	/**
	* Returns the value of a field
	* @method public getValue
	* @class aimsRecord
	* @param string Field The name of the field for which to retrieve the value.
	* @return misc Value in field. Note: FALSE is returned when field is not found!
	*/
	function getValue($fld) {
		if (!array_key_exists($fld,$this->_values)) return FALSE;
		return $this->_values[$fld];
	}
}

/**
*
* The objects of the class contain attribute information for {@link aimsFeatureLayer} after
* a {@link aimsMap::queryLayer} request. It can be access by {@link aimsFeatureLayer::Recordset}.<br>
* The aimsRecordset contains a list of all the fieldnames as well as the individual
* {@link aimsRecord}. The fieldnames can be access through the $myRS->Fields array, where
* the number of fields are stored in $myRS->NumFields. Do NOT change the values of this
* class members, unless you excatly know what you are doing!<br>
* The records in the recordset can be access through different methods described below.<br>
* Note: currently the fieldname list and the fields in the records are not related. They
* should contain the same fields, but there's nothing to prevent the recordset to have
* records of different length and different fields! Some more rigerous checking still needs to
* be implemented.
*
* @class aimsRecordset
* @package phparcims
*/
class aimsRecordset{
	var $CurrentRecord;
	/**
	* The number of records in the recordset (Read-only).
	* @class aimsRecordset
	* @attribute public int NumRecords
	*/
	var $NumRecords;
	/**
	* A {@link aimsEnvelope} containing all the records in the recordset.
	* This is only valid for spatial recordsets.
	* @class aimsRecordset (Read-only).
	* @attribute public object Envelope
	*/
	var $Extent; // optional; only used for spatial recordsets

	/**
	* The fields ({@link aimsField}) of the recordset.
	* @class aimsRecordset
	* @attribute private array _fields
	*/
	var $_fields;
	var $_records;
	var $_layer;
	/**
	* Creates an empty recordset
	* @constructor aimsRecordset
	* @param object Layer Layer to which the recordset belongs.
	* @return bool TRUE if was created, FALSE otherwise.
	*/
	function aimsRecordset(&$layer){
		$this->_fields=array();
		$this->NumFields=0;
		$this->clearRecords();
		$this->Envelope=0;
		if (!is_a($layer,'aimsLayer')) return FALSE;
		$this->_layer=&$layer;
		$layer->Recordset=&$this;
		return TRUE;
	}
	/**
	* Adds a new field to the record set.
	* If a field with the same name already exists it will be overwritten by the new field. Also,
	* once a record was added to the recordset, no more fields can be added.
	* @method addField
	* @class aimsRecordset
	* @param string Field The name of the field to be added
	* @return bool TRUE if field was added, FALSE otherwise (check {@link aimsMap::ErrorCode} for error).
	*/
	function addField($fld){
		if (count($this->_records)>0) {
			$this->_layer->_map->setError(5520,'Can not add field to recordset with records.');
			return FALSE;
   		}
		if (!is_a($fld,'aimsField')) {
			$this->_layer->_map->setError(5410,'The supplied object is not of type aimsField.');
			return FALSE;
		}
		$this->_fields[$fld->Name]=$fld;
		return TRUE;
	}

	/**
	* Returns the field object for the field with name aName.
	* @class aimsRecordset
	* @method public getField
	* @param string aFieldname The field name.
	* @return mixed The field object if field with aName was found. FALSE otherwise.
	*/
	function getField($fld) {
		if (!array_key_exists($fld,$this->_fields)) return FALSE;
		return $this->_fields[$fld];
	}

	/**
	* Returns the field with the name Name.
	* If the parameter Index  is supplied and the field is found. It will contains the
	* the index of the field in the records. The index can then be used to retrieve
	* the value in the field.
	* @class aimsRecordset
	* @method public getFieldByName
	* @param string aName The name of the field to return.
	* @param object aField The variable will hold the field ({@link aimsField}) object if found.
	* @param bool TRUE if field is found, FALSE otherwise.
	*/
	function &getFieldByName($name, &$fld) {
		foreach ($this->_fields as $f) {
			if ($f->Name==$name) {
				$fld=$f;
				return TRUE;
			}
		}
		return FALSE;
	}
	/**
	* Adds a new, empty {@link aimsRecord} to the recordset.
	* Call this function to add new records to the recordset. <b>NOTE</b>: this
	* method does not add new records to the underlying data! It's only added
	* during the life of this recordset. So in most cases you won't need to use
	* this method.
	* @method public newRecord
	* @class aimsRecordset
	* @return object The new record object ({@link aimsRecord}).
	*/
	function &newRecord(){
		$v = new aimsRecord($this);
		$this->_records[$this->NumRecords++]=&$v;
		return $v;
	}
	/**
	* Clears all the current records.
	* @class aimsRecordset
	* @method public clearRecords
	*/
	function clearRecords() {
		$this->CurrentRecord=-1;
		$this->_records=array();
		$this->NumRecords=0;
	}
	/**
	* Moves the record cursor to the next record and returns the record.
	* If the end of the recordset is reached, FALSE is returned.
	* @brief Returns the next record.
	* @method getNext
	* @class aimsRecordset
	* @return misc 0 if end of recordset, otherwise an {@link aimsRecord} object
	*/
	function getNext(){
		$this->_currentRecord++;
		if ($this->_currentRecord<$this->NumRecords)
			return $this->_records[$this->_currentRecord];
		return FALSE;
	}
	/**
	* Moves the recordset cursor to the previous record and returns the record.
	* If the beginning of the recordset is reached, 0/false is returned.
	* @brief Returns the previous record.
	* @method getPrevious
	* @class aimsRecordset
	* @returns misc 0 if beginning of recordset is reached, otherwise an {@link aimsRecord} object.
	*/
	function getPrevious(){
		$this->_currentRecord--;
		if ($this->_currentRecord>-1)
			return $this->_records[$this->_currentRecord];
		return FALSE;
	}
	/**
	* Returns the current record.
	* @method getCurrent
	* @class aimsRecordset
	* @package phparcims
	* @returns object The current {@link aimsRecord} pointed to by the recordset cursor.
	*/
	function getCurrent(){
		return $this->_records[$this->_currentRecord];
	}
	/**
	* Moves the recordset cursor to the i<sup>th</sup> record in the recordset. The index
	* of records start with 0 and goes to N-1, where N = number of records in recordset.
	* @brief Move to the i<sup>th</sup> record.
	* @method moveTo
	* @class aimsRecordset
	* @package phparcims
	* @param integer theIndex The index of the i<sup>th</sup> record.
	* @returns boolean TRUE if record exists, FALSE otherwise.
	*/
	function moveTo($i){
		if ($this->NumRecords>0) {
			if (($i>-1) && ($i<$this->NumRecords))
				$this->_currentRecord=$i;
			return TRUE;
		}
		return FALSE;
	}
	/**
	* Moves to the last record in the recordset.
	* @method moveLast
	* @class aimsRecordset
	* @package phparcims
	* @returns boolean TRUE if record exists, FALSE otherwise.
	*/
	function moveLast(){
		if ($this->NumRecords>0) {
			$this->_currentRecord=$this->NumRecord-1;
			return TRUE;
		}
		return FALSE;
	}
	/**
	* Moves to the first record in the recordset.
	* @method moveFirst
	* @class aimsRecordset
	* @package phparcims
	* @returns boolean TRUE if record exists, FALSE otherwise.
	*/
	function moveFirst(){
		if ($this->NumRecords>0) {
			$this->_currentRecord=0;
			return TRUE;
		}
		return FALSE;
	}
}

/* ******************** Query classes (START)**********************************/
/**
* The object should not be used directly. The {@link aimsAttributeQuery} or {@link aimsSpatialQuery}
* should be used instead
*
* Some info about queries:
* <blockquote style="font-family: arial; background-color:#EEEEEE">
* <b>Querying with dates</b>:<br>
* The syntax for querying dates is the same regardless of the locale. A date query uses the following syntax: {ts 'YYYY-MM-DD hh:mi:ss'}
* where:<br>
* <table border="1">
* <tr><td>YYYY</td><td>Year</td><td> Required Use four digits for the year.</td></tr>
* <tr><td>MM</td><td>Month (01-12)</td><td>Required Use two digits for the month. March is 03.</td></tr>
* <tr><td>DD</td><td>Day (01-31)</td><td>Required Use two digits for the day. The fourth is 04. </td></tr>
* <tr><td>hh</td><td>Hour (00-23)</td><td>Optional Use a 24-hour clock. 8 a.m. is 08, and 8 p.m. is 20. </td></tr>
* <tr><td>mi</td><td>Minutes (00- 59)</td><td>Optional Use two digits for the minutes. If minutes is used, hours must also be included. </td></tr>
* <tr><td>ss</td><td>Seconds (00- 59)</td><td>Optional Use two digits for the seconds. If seconds is used, hours and minutes must also be included.</td></tr>
* </table>
* The year, month, and day are each separated by a dash (-). The hour, minutes, and seconds are each separated by a colon (:). The date
* is enclosed in single quotes (') inside curly brackets ({}). Before the date, ts (for time stamp) must be included.
* <p>For 8:03:23 a.m. January 4, 2000, the query on a DBF file looks like: "MYDATE = {ts '2000-01-04 08:03:32'}"</p>
* <p>For 9:18 p.m. March 8, 2002, the query on an ArcSDE layer looks like: "ARCSDE.TABLE.MYDATE = {ts '2002-03-08 21:18:00'}"</p>
* <p><sup>(ESRI)</sup></p></blockquote>
* @class aimsQuery
* @package phparcims
*/
class aimsQuery{
	/**
	* Defines "where" part of SQL expression. Required when jointables attribute for ArcSDE tables is used.<sup>(ESRI)<sup>
	* @brief The Where clause of the query.
	* @class aimsQuery
	* @attribute public string Where
	*/
	var $Where;
	/**
	* In general
	* @brief
	* @class aimsQuery
	* @attribute public string Accuracy
	*/
	var $Accuracy;
	/**
	* <blockquote style="font-family: arial; background-color:#EEEEEE">
	* Maximum number of features to be extracted that meet criteria. The value for this featurelimit overrides any
	* featurelimit set in {@link aimsMap::QueryFeatureLimit}.
	* <p><sup>(ESRI)</sup></p></blockquote>
	* @brief Maximum number of features to be extracted that meet criteria.
	* @class aimsQuery
	* @attribute public string Featurelimit
	*/
	var $Featurelimit;
	/**
	* In general
	* @brief
	* @class aimsQuery
	* @attribute public string JoinExp
	*/
	var $JoinExp;
	/**
	* In general
	* @brief
	* @class aimsQuery
	* @attribute public string JoinTable
	*/
	var $JoinTable;
	/**
	* <blockquote style="font-family: arial; background-color:#EEEEEE">
	* List of fields available for querying or extracting. Multiple fields can be included in the subfields list.
	* Fields must be separated by blank space.
	* <p>If subfields is not used, all fields are returned. If subfields is used, only listed fields are returned.</p>
	* The subfields list can include fields from the layer table or a joined table.
	* <ul><li>For shapefiles with no joined tables, the field can be referenced using the short format. field="AREA"</li>
	* <li>For shapefiles with joined tables, the name of the joined table must be included along with the field. field="JOINEDTABLE.AREA"</li>
	* <li>For ArcSDE layers with or without joined tables, the field must be referenced using the full long format. field="ARCSDENAME.TABLE.AREA"</li>
	* </ul>
	* <p><sup>(ESRI)</sup></p></blockquote>
	* @brief List of subfields returned by the query.
	* @class aimsQuery
	* @attribute public string Subfields
	*/
	var $Subfields;

	var $_featurecoordsys;
	var $_buffer;

	/**
	*
	* The SubField paramter is a string containing the field names seperated by a blank space; if all fields should be included
	* use #ALL#; also spatial queries require the following fields #ID# and #SHAPE# in order to work;
	* when working with joint tables, the table name needs to preceed the field name (e.g. MYTABLE.MYFIELD)
	*
	* @constructor aimsQuery
	* @param string Where The were clause of the query
	* @param optional string SubFields The attribute fields that should returned
	* @param optional string Accuracy
	* @param optional int FeatureLimit Specifies how many features should be returned by the query (default=25)
	* @param optional string JointExpression  The expression used to join the tables
	* @param optional string JointTable The table that should be joint for the query
	*/
	function aimsQuery($w,$s='',$a='',$fl=25,$jexp='',$jtbl=''){
		$this->Where=$w;
		if ($a) $this->Accuracy=$a;
		if ($fl) $this->Featurelimit=$fl;
		if ($jexp) $this->JoinExp=$jexp;
		if ($jtbl) $this->JoinTable=$jtbl;
		if ($s) $this->Subfields=$s;
	}

	/**
	* Sets the where clause of the spatial query
	* @method setWhereClause
	* @class aimsQuery
	* @param string Where The full where clause as used in SQL.
	* @return int 0 if where clause could not be applied, 1 otherwise
	*/
	function setWhereClause($w) {
		$this->Where=$w;
		return 1;
	}
	
	/**
	* Sets the {@link aimsFeatureCoordsys} to which the query results should be applies.
	* @class aimsQuery
	* @method public setFeatureCoordsys
	* @param object CoordSystem The feature coordinate system.
	* @return bool TRUE if the coordsystem was assigned. FALSE otherwise.
	*/
	function setFeatureCoordsys($f) {
		if (!is_a($f,'aimsFeatureCoordsys')) {
			return FALSE;
		}
		$this->_featurecoordsys=$f;
		return TRUE;
	}
	/**
	* Removes the {@link aimsFeatureCoordsys} from the query.
	* @class aimsQuery
	* @method public removeFeatureCoordsys
	* @return void
	*/
	function removeFeatureCoordsys() {
		unset($this->_featurecoordsys);
	}
	
	/**
	* @class aimsQuery
	* @method setBuffer
	* @param object Buffer The buffer object.
	* @return bool TRUE if the buffer was added. FALSE otherwise.
	*/
	function setBuffer($b) {
		if (!is_a($b,'aimsBuffer')) {
			return FALSE;
		}
		$this->_buffer=$b;
		return TRUE;
	}

	/**
	* Removes the current buffer;
	* @class aimsQuery
	* @method public removeBuffer
	* @return void
	*/
	function removeBuffer() {
		unset($this->_buffer);
	}

	/**
	* Return the full tag of the object as used/defined by ArcXML
	* @class aimsQuery
	* @method private returnTag
	* @param string Parent the name of the parent tag
	* @returns string The full tag with attributes and any possible sub-tags
	*/
	function returnTag($parent=''){
		if ($this->Where) {
			$w = str_replace('<','&lt;',
					str_replace('"','&quot;',
							str_replace("'",'&apos;',
								str_replace('>','&gt;',
									str_replace('&','&amp',$this->Where)))));
			$w=str_replace("'","&apos;",$w);
			$TagStr='where="'.$w.'" ';
		}
		if ($this->Accuracy) $TagStr.='accuracy="'.$this->Accuracy.'" ';
		if ($this->Featurelimit) $TagStr.='featurelimit="'.$this->Featurelimit.'" ';
		if ($this->JoinExp) $TagStr.='joinexpression="'.$this->JoinExp.'" ';
		if ($this->JoinTable) $TagStr.='jointables="'.$this->JoinTable.'" ';
		if ($this->Subfields) $TagStr.='subfields="'.$this->Subfields.'" ';
		return $TagStr;
	}
}

/**
* Use this class to create an attribute query. Please see {@link aimsQuery} on more info when
* creating a query with date values.
*
* @class aimsAttributeQuery
* @package phparcims
* @inherited aimsQuery
*/
class aimsAttributeQuery extends aimsQuery {
	
	/**
	* (still needs documented)
	* @constructor aimsAttributeQuery
	* @param string Where The were clause of the query.
	* @param optional string SubFields The attribute fields that should returned  (see {@link aimsQuery}).
	* @param optional string Accuracy The accuracy of the ...
	* @param optional int FeatureLimit Specifies how many features should be returned by the query (default=25).
	* @param optional string JoinExpression  The expression used to join the tables.
	* @param optional string JoinTable The name of the join table.
	*/
	function aimsAttributeQuery($w,$s='',$a='',$fl=25,$jexp='',$jtbl='') {
		parent::aimsQuery($w,$s,$a,$fl,$jexp,$jtbl);
	}
	/**
	* Return the full tag of the object as used/defined by ArcXML
	* @class aimsAttributeQuery
	* @method private returnTag
	* @param string Parent the name of the parent tag
	* @returns string The full tag with attributes and any possible sub-tags
	*/
	function returnTag($parent='') {
		$tagStr='<QUERY '.parent::returnTag($parent).'>';
		if (isset($this->_buffer)) $tagStr.=$this->_buffer->returnTag('QUERY');
		if ($parent=='GET_FEATURES') {
			if (isset($this->_featurecoordsys)) $tagStr.=$this->_featurecoordsys->returnTag();			
		}
		return $tagStr.'</QUERY>';
	}
}

/**
* aimsSpatialQueries can be used in three ways:<ol>
* <li>A tabular query based on the value of attributes. For example, an attribute query asks for all cities
* in Canada where the population is greater than 500,000.<sup>(ESRI)</sup> For this, one needs to specify only
* the WHERE parameter during object initialization.</li>
* <li>A spatial query based on features selected on a map. For example, a rectangle might be
* dragged over the eastern United States to select a group of cities.<sup>(ESRI)</sup> In this case don't specify the
* WHERE parameter during object initialization, but specify the spatial filter ({@link aimsSpatialQuery::setSpatialFilter}).</li>
* <li>A combination of a tabular and spatial query. For example, a group of cities might be selected, but only cities
* with a population greater than 500,000 are displayed.<sup>(ESRI)</sup> Specify the WHERE parameter and the spatial filter.</li>
* </ol>
* It can also be used to join attribute tables from shapefiles (DBF) and ArcSDE tables.
* @class aimsSpatialQuery
* @package phparcims
* @inherited aimsQuery
*/
class aimsSpatialQuery extends aimsQuery{
	/**
	* <blockquote style="font-family: arial; background-color:#EEEEEE">
	* Spatialfirst" processes the spatial part of the query
	* before the attribute part. "Attributefirst" processes the attribute part of
	* the query first. If "optimize" is used, ArcSDE will make the judgment whether
	* to pick "spatialfirst" or "attributefirst.
	* <p><sup>(ESRI)</sup></p></blockquote>
	* @brief Specifies if the spatial or attribute query should be performed first.
	* @class aimsSpatialQuery
	* @attribute public string Searchorder
	*/
	var $Searchorder;
	/**
	* The spatial filter to search features.
	* @class aimsSpatialQuery
	* @attribute private object _spatialFilter
	*/
	var $_spatialFilter;
	/**
	* The spatial relation to compare features and the spatial filter.
	* @class aimsSpatialQuery
	* @attribute private string _spatialFilterRelation
	*/
	var $_spatialFilterRelation;
	var $_filtercoordsys;

	/**
	* Notes:
	* <ul>
	* <li>The WHERE parameter is required when the JOINTTABLE paramter for ArcSDE layers is used. Otherwise,
	* this parameter is optional.<sup>(ESRI)</sup></li>
	* <li>When joining attributes of shapefile layers, they can be joined only to DBF files located in the same
	* directory. A joined DBF file cannot be another shapefile DBF file that is currently
	* being used in an ArcIMS service.<sup>(ESRI)</sup></li>
	* <li>DBF joinjointable names are limited to 10 characters."</i><sup>(ESRI)</sup></li>
	* <li>When joining tables in ArcSDE, ArcSDE layers can only be joined to other tables in the relational database
	* management system (RDBMS).<sup>(ESRI)</sup></li>
	* <li>When using subfields, the number of fields that can be listed is limited to 254.<sup>(ESRI)</sup></li>
	* </ul>
	*
	* @constructor aimsSpatialQuery
	* @param optional string Where The were clause of the query (see {@link aimsQuery::Where} for details).
	* @param optional string SubFields The attribute fields that should returned (see {@link aimsQuery::Subfields})
	* @param optional string Accuracy
	* @param optional int FeatureLimit Specifies how many features should be returned by the query (default=25)
	* @param optional string JointExpression  The expression used to join the tables
	* @param optional string JointTable The table that should be joint for the query
	* @param string SearchOrder the order in which to perform the query
	*/
	function aimsSpatialQuery($w='',$s='',$a='',$fl=25,$jexp='',$jtbl='',$o=''){
		parent::aimsQuery($w,$s,$a,$fl,$jexp,$jtbl);
		if ($o) $this->Searchorder=$o;
	}

	/**
	* Adds the spatial filter for the queries. The spatial filter can be one of the
	* following objects {@link aimsEnvelope}, {@link aimsMultipoint}, {@link aimsPolygon} or
	* {@link aimsPolyline}. If it's not specified, the query will perform only an
	* attribute search<br>
	* The {@link aimsSpatialQuery::_spatialFilterRelation} parameter can have one of the following
	* values: 
	* <ul>
	* <li>'area_intersection': all feature partially or fully within the filter are returned;
	*	this works on the shapes of the filter and features and therefore can be slow</li>
	* <li>'envelope_intersection: selected features based on the overlap of their bounding box (extent)
	*	 and the bounding box of the filter; use this if speed is important</li>
	* </ul>
	* 
	* @method setSpatialFilter
	* @class aimsSpatialQuery
	* @param object Filter The spatial Object which is used to select the feature
	* @param string Relation The spatial relation used to select feature with the Filter
	* @return int 0 if filter could not be applied, 1 if filter was applied successfully
	*/
	function setSpatialFilter($f,$r){
	//input $f is one of: aimsEnvelope, aimsMultipoint, aimsPolygon, aimsPolyline
		if (!(is_a($f,'aimsPolyline') || is_a($f,'aimsEnvelope') || is_a($f,'aimsMultipoint') || is_a($f,'aimsPolygon'))) {
				return FALSE;
		}
		if (!in_array(strtolower($r),array('area_intersection','envelope_intersection'))) {
			return FALSE;
		}
		$this->_spatialFilter=$f;
		$this->_spatialFilterRelation=strtolower($r);
		return TRUE;
	}

	/**
	* Remove the current spatial filter.
	* @method public removeSpatialFilter
	* @return void
	*/
	function removeSpatialFilter(){
		unset($this->_spatialFilter);
		unset($this->_spatialFilterRelation);
	}

	/**
	* Sets the {@link aimsFilterCoordsys} of the current client.
	* This should be used in the case the client uses a different coordsystem than
	* the map service.
	* @class aimsQuery
	* @method public setFilterCoordsys
	* @param object CoordSystem The feature coordinate system.
	* @return bool TRUE if the coordsystem was assigned. FALSE otherwise.
	*/
	function setFilterCoordsys($f) {
		if (!is_a($f,'aimsFilterCoordsys')) {
			return FALSE;
		}
		$this->_filtercoordsys=$f;
		return TRUE;
	}
	/**
	* Removes the {@link aimsFilterCoordsys} from the query.
	* @class aimsQuery
	* @method public removeFilterCoordsys
	* @return void
	*/
	function removeFilterCoordsys() {
		unset($this->_filtercoordsys);
	}

	/**
	* Return the full tag of the object as used/defined by ArcXML
	* @class aimsSpatialQuery
	* @method public returnTag
	* @param string Parent the name of the parent tag
	* @returns string The full tag with attributes and any possible sub-tags
	*/
	function returnTag($parent=''){
		$TagStr='<SPATIALQUERY ';
		$TagStr.=parent::returnTag($parent);
		if ($this->Searchorder) $TagStr.='searchorder="'.$this->Searchorder.'" >';
		else $TagStr.=' >';
		switch ($parent) {
		case 'GET_FEATURES':
			if ($this->_filtercoordsys) $this->_filtercoordsys->returnTag();
			if ($this->_featurecoordsys) $this->_featurecoordsys->returnTag();
		case 'LAYER':
		case 'LAYERDEF':
		case 'BUFFER':
			if ($this->_spatialFilter) {
				$TagStr.='><SPATIALFILTER relation="'.$this->_spatialFilterRelation.'">';
				$TagStr.=$this->_spatialFilter->returnTag();
				if ($this->_buffer) {
					$TagStr.=$this->_buffer->returnTag('SPATIALQUERY');
				}
				$TagStr.='</SPATIALFILTER>';
			}
		}
		return $TagStr.'</SPATIALQUERY>';
	}
}

/**
* The buffer object can be used in three different ways:
* <ul>
* <li>buffer a region around one or more selected features. For this, add the buffer object to
a {@link aimsSpatialQuery} object, yet don't specify a spatial filter. This will just display the
buffer, but not select any features.</li>
* <li>select features from the same or another layer that fall within the buffer. For this specify the
* {@link aimsBuffer::TargetLayerID}, add the buffer object to the {@link aimsSpatialQuery} object,
* yet don't specify a spatial filter for the query. This can be used to display ({@link aimsMap::draw}) as well as retrieve the
* selected features ({@link aimsMap::queryLayer}).</li>
* <li>use it as selection tool. In this case, you need to specify a {@link aimsSpatialQuery::setSpatialFilter} and the buffer for the
* {@link aimsSpatialQuery}. The buffer is applied around the spatialfilter object and the resulting area is then used
* to perform the selection on the layer. This can be used to display ({@link aimsMap::draw}) as well as retrieve the
* selected features ({@link aimsMap::queryLayer}).</li>
* </ul>
* @class aimsBuffer
* @package phparcims
*/
class aimsBuffer {
	/**
	* Buffer area width in buffer units.<sup>(ESRI)</sup>
	* @class aimsBuffer
	* @attribute public int Distance
	*/
	var $Distance;
	/**
	* Specifies units that apply to the buffer.<sup>(ESRI)</sup>
	* @class aimsBuffer
	* @attribute public int BufferUnits
	*/
	var $BufferUnits;
	/**
	* Generated buffers are projected if {@link aimsFeatureCoordsys} is specified for the layer.
	* If <b>DoProject</b> is TRUE, the buffer will be project, if FALSE it won't.
	* @class aimsBuffer
	* @attribute public bool DoProject
	*/
	var $DoProject;

	/**
	* Defined the targer layer for selecting features based on a buffer in the same of different layer.<sup>(ESRI)</sup>
	* @class aimsBuffer
	* @attribute public string TargetLayerID
	*/
	var $TargetLayerID;

	/**
	*
	* @brief Constructor for the aimsBuffer class.
	* @constructore aimsBuffer
	* @package phparcims
	* @param int Distance The width of the buffer area.
	*/
	function aimsBuffer ($d) {
		$this->Distance=$d;
		$this->DoProject=FALSE;
	}

	function returnTag($parent='') {
		$TagStr='<BUFFER ';
		$TagStr.='distance="'.$this->Distance.'" ';
		if (isset($this->BufferUnits)) $TagStr.='bufferunits="'.$this->BufferUnits.'" ';
		$TagStr.='project="'.($this->DoProject?'true':'false').'" ';
		$TagStr.='>';
		if ($parent=='SPATIALQUERY') {
			$TagStr.='<TARGETLAYER id="'.$this->TargetLayerID.'" />';
			//$TagStr.=$this->S
		}
		return $TagStr.='</BUFFER>';
	}
}

?>
Return current item: PhpArcIMS