Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?php/*** Author : Julien Moquet** Inspired by Proj4js from Mike Adair madairATdmsolutions.ca* and Richard Greenwood rich@greenwoodmap.com* License: LGPL as per: http://www.gnu.org/copyleft/lesser.html*/class Proj4phpProj {/*** Property: readyToUse* Flag to indicate if initialization is complete for $this Proj object*/public $readyToUse = false;/*** Property: title* The title to describe the projection*/public $title = null;/*** Property: projName* The projection class for $this projection, e.g. lcc (lambert conformal conic,* or merc for mercator). These are exactly equivalent to their Proj4* counterparts.*/public $projName = null;/*** Property: projection* The projection object for $this projection. */public $projection = null;/*** Property: units* The units of the projection. Values include 'm' and 'degrees'*/public $units = null;/*** Property: datum* The datum specified for the projection*/public $datum = null;/*** Property: x0* The x coordinate origin*/public $x0 = 0;/*** Property: y0* The y coordinate origin*/public $y0 = 0;/*** Property: localCS* Flag to indicate if the projection is a local one in which no transforms* are required.*/public $localCS = false;/**** @var type*/protected $wktRE = '/^(\w+)\[(.*)\]$/';/*** Constructor: initialize* Constructor for Proj4php::Proj objects** Parameters:* $srsCode - a code for map projection definition parameters. These are usually* (but not always) EPSG codes.*/public function __construct( $srsCode ) {$this->srsCodeInput = $srsCode;//check to see if $this is a WKT stringif( (strpos( $srsCode, 'GEOGCS' ) !== false) ||(strpos( $srsCode, 'GEOCCS' ) !== false) ||(strpos( $srsCode, 'PROJCS' ) !== false) ||(strpos( $srsCode, 'LOCAL_CS' ) !== false) ) {$this->parseWKT( $srsCode );$this->deriveConstants();$this->loadProjCode( $this->projName );return;}// DGR 2008-08-03 : support urn and urlif( strpos( $srsCode, 'urn:' ) === 0 ) {//urn:ORIGINATOR:def:crs:CODESPACE:VERSION:ID$urn = explode( ':', $srsCode );if( ($urn[1] == 'ogc' || $urn[1] == 'x-ogc') &&($urn[2] == 'def') &&($urn[3] == 'crs') ) {$srsCode = $urn[4] . ':' . $urn[strlen( $urn ) - 1];}} else if( strpos( $srsCode, 'http://' ) === 0 ) {//url#ID$url = explode( '#', $srsCode );if( preg_match( "/epsg.org/", $url[0] ) ) {// http://www.epsg.org/#$srsCode = 'EPSG:' . $url[1];} else if( preg_match( "/RIG.xml/", $url[0] ) ) {//http://librairies.ign.fr/geoportail/resources/RIG.xml#//http://interop.ign.fr/registers/ign/RIG.xml#$srsCode = 'IGNF:' . $url[1];}}$this->srsCode = strtoupper( $srsCode );if( strpos( $this->srsCode, "EPSG" ) === 0 ) {$this->srsCode = $this->srsCode;$this->srsAuth = 'epsg';$this->srsProjNumber = substr( $this->srsCode, 5 );// DGR 2007-11-20 : authority IGNF} else if( strpos( $this->srsCode, "IGNF" ) === 0 ) {$this->srsCode = $this->srsCode;$this->srsAuth = 'IGNF';$this->srsProjNumber = substr( $this->srsCode, 5 );// DGR 2008-06-19 : pseudo-authority CRS for WMS} else if( strpos( $this->srsCode, "CRS" ) === 0 ) {$this->srsCode = $this->srsCode;$this->srsAuth = 'CRS';$this->srsProjNumber = substr( $this->srsCode, 4 );} else {$this->srsAuth = '';$this->srsProjNumber = $this->srsCode;}$this->loadProjDefinition();}/*** Function: loadProjDefinition* Loads the coordinate system initialization string if required.* Note that dynamic loading happens asynchronously so an application must* wait for the readyToUse property is set to true.* To prevent dynamic loading, include the defs through a script tag in* your application.**/public function loadProjDefinition() {//check in memoryif( array_key_exists( $this->srsCode, Proj4php::$defs ) ) {$this->defsLoaded();return;}//else check for def on the server$filename = dirname( __FILE__ ) . '/defs/' . strtoupper( $this->srsAuth ) . $this->srsProjNumber . '.php';try {Proj4php::loadScript( $filename );$this->defsLoaded(); // succes} catch ( Exception $e ) {$this->loadFromService(); // fail}}/*** Function: loadFromService* Creates the REST URL for loading the definition from a web service and* loads it.*** DO IT AGAIN. : SHOULD PHP CODE BE GET BY WEBSERVICES ?*/public function loadFromService() {//else load from web service$url = Proj4php::$defsLookupService . '/' . $this->srsAuth . '/' . $this->srsProjNumber . '/proj4/';try {Proj4php::$defs[strtoupper($this->srsAuth) . ":" . $this->srsProjNumber] = Proj4php::loadScript( $url );} catch ( Exception $e ) {$this->defsFailed();}}/*** Function: defsLoaded* Continues the Proj object initilization once the def file is loaded**/public function defsLoaded() {$this->parseDefs();$this->loadProjCode( $this->projName );}/*** Function: checkDefsLoaded* $this is the loadCheck method to see if the def object exists**/public function checkDefsLoaded() {return isset(Proj4php::$defs[$this->srsCode]) && !empty(Proj4php::$defs[$this->srsCode]);}/*** Function: defsFailed* Report an error in loading the defs file, but continue on using WGS84**/public function defsFailed() {Proj4php::reportError( 'failed to load projection definition for: ' . $this->srsCode );Proj4php::$defs[$this->srsCode] = Proj4php::$defs['WGS84']; //set it to something so it can at least continue$this->defsLoaded();}/*** Function: loadProjCode* Loads projection class code dynamically if required.* Projection code may be included either through a script tag or in* a built version of proj4php** An exception occurs if the projection is not found.*/public function loadProjCode( $projName ) {if( array_key_exists( $projName, Proj4php::$proj ) ) {$this->initTransforms();return;}//the filename for the projection code$filename = dirname( __FILE__ ) . '/projCode/' . $projName . '.php';try {Proj4php::loadScript( $filename );$this->loadProjCodeSuccess( $projName );} catch ( Exception $e ) {$this->loadProjCodeFailure( $projName );}}/*** Function: loadProjCodeSuccess* Loads any proj dependencies or continue on to final initialization.**/public function loadProjCodeSuccess( $projName ) {if( isset(Proj4php::$proj[$projName]->dependsOn) && !empty(Proj4php::$proj[$projName]->dependsOn)) {$this->loadProjCode( Proj4php::$proj[$projName]->dependsOn );} else {$this->initTransforms();}}/*** Function: defsFailed* Report an error in loading the proj file. Initialization of the Proj* object has failed and the readyToUse flag will never be set.**/public function loadProjCodeFailure( $projName ) {Proj4php::reportError( "failed to find projection file for: " . $projName );//TBD initialize with identity transforms so proj will still work?}/*** Function: checkCodeLoaded* $this is the loadCheck method to see if the projection code is loaded**/public function checkCodeLoaded( $projName ) {return isset(Proj4php::$proj[$projName]) && !empty(Proj4php::$proj[$projName]);}/*** Function: initTransforms* Finalize the initialization of the Proj object**/public function initTransforms() {$this->projection = clone(Proj4php::$proj[$this->projName]);Proj4php::extend( $this->projection, $this );$this->init();// initiate depending classif( false !== ($dependsOn = isset($this->projection->dependsOn) && !empty($this->projection->dependsOn) ? $this->projection->dependsOn : false) ) {Proj4php::extend( Proj4php::$proj[$dependsOn], $this->projection) &&Proj4php::$proj[$dependsOn]->init() &&Proj4php::extend( $this->projection, Proj4php::$proj[$dependsOn] );}$this->readyToUse = true;}/****/public function init() {$this->projection->init();}/**** @param type $pt* @return type*/public function forward( $pt ) {return $this->projection->forward( $pt );}/**** @param type $pt* @return type*/public function inverse( $pt ) {return $this->projection->inverse( $pt );}/*** Function: parseWKT* Parses a WKT string to get initialization parameters**/public function parseWKT( $wkt ) {if( false === ($match = preg_match( $this->wktRE, $wkt, $wktMatch )) )return;$wktObject = $wktMatch[1];$wktContent = $wktMatch[2];$wktTemp = explode( ",", $wktContent );$wktName = (strtoupper($wktObject) == "TOWGS84") ? "TOWGS84" : array_shift( $wktTemp );$wktName = preg_replace( '/^\"/', "", $wktName );$wktName = preg_replace( '/\"$/', "", $wktName );/*$wktContent = implode(",",$wktTemp);$wktArray = explode("],",$wktContent);for ($i=0; i<sizeof($wktArray)-1; ++$i) {$wktArray[$i] .= "]";}*/$wktArray = array();$bkCount = 0;$obj = "";foreach( $wktTemp as $token ) {$bkCount = substr_count($token, "[") - substr_count($token, "]");// ???$obj .= $token;if( $bkCount === 0 ) {array_push( $wktArray, $obj );$obj = "";} else {$obj .= ",";}}//do something based on the type of the wktObject being parsed//add in variations in the spelling as requiredswitch( $wktObject ) {case 'LOCAL_CS':$this->projName = 'identity';$this->localCS = true;$this->srsCode = $wktName;break;case 'GEOGCS':$this->projName = 'longlat';$this->geocsCode = $wktName;if( !$this->srsCode )$this->srsCode = $wktName;break;case 'PROJCS':$$this->srsCode = $wktName;break;case 'GEOCCS':break;case 'PROJECTION':$this->projName = Proj4php::$wktProjections[$wktName];break;case 'DATUM':$this->datumName = $wktName;break;case 'LOCAL_DATUM':$this->datumCode = 'none';break;case 'SPHEROID':$this->ellps = $wktName;$this->a = floatval( array_shift( $wktArray ) );$this->rf = floatval( array_shift( $wktArray ) );break;case 'PRIMEM':$this->from_greenwich = floatval( array_shift( $wktArray ) ); //to radians?break;case 'UNIT':$this->units = $wktName;$this->unitsPerMeter = floatval( array_shift( $wktArray ) );break;case 'PARAMETER':$name = strtolower( $wktName );$value = floatval( array_shift( $wktArray ) );//there may be many variations on the wktName values, add in case//statements as requiredswitch( $name ) {case 'false_easting':$this->x0 = $value;break;case 'false_northing':$this->y0 = $value;break;case 'scale_factor':$this->k0 = $value;break;case 'central_meridian':$this->long0 = $value * Proj4php::$common->D2R;break;case 'latitude_of_origin':$this->lat0 = $value * Proj4php::$common->D2R;break;case 'more_here':break;default:break;}break;case 'TOWGS84':$this->datum_params = $wktArray;break;//DGR 2010-11-12: AXIScase 'AXIS':$name = strtolower( $wktName );$value = array_shift( $wktArray );switch( $value ) {case 'EAST' : $value = 'e';break;case 'WEST' : $value = 'w';break;case 'NORTH': $value = 'n';break;case 'SOUTH': $value = 's';break;case 'UP' : $value = 'u';break;case 'DOWN' : $value = 'd';break;case 'OTHER':default : $value = ' ';break; //FIXME}if( !$this->axis ) {$this->axis = "enu";}switch( $name ) {case 'X': $this->axis = $value . substr( $this->axis, 1, 2 );break;case 'Y': $this->axis = substr( $this->axis, 0, 1 ) . $value . substr( $this->axis, 2, 1 );break;case 'Z': $this->axis = substr( $this->axis, 0, 2 ) . $value;break;default : break;}case 'MORE_HERE':break;default:break;}foreach( $wktArray as $wktArrayContent )$this->parseWKT( $wktArrayContent );}/*** Function: parseDefs* Parses the PROJ.4 initialization string and sets the associated properties.**/public function parseDefs() {$this->defData = Proj4php::$defs[$this->srsCode];#$paramName;#$paramVal;if( !$this->defData ) {return;}$paramArray = explode( "+", $this->defData );for( $prop = 0; $prop < sizeof( $paramArray ); $prop++ ) {if( strlen( $paramArray[$prop] ) == 0 )continue;$property = explode( "=", $paramArray[$prop] );$paramName = strtolower( $property[0] );if( sizeof( $property ) >= 2 ) {$paramVal = $property[1];}switch( trim( $paramName ) ) { // trim out spacescase "": break; // throw away nameless parametercase "title": $this->title = $paramVal;break;case "proj": $this->projName = trim( $paramVal );break;case "units": $this->units = trim( $paramVal );break;case "datum": $this->datumCode = trim( $paramVal );break;case "nadgrids": $this->nagrids = trim( $paramVal );break;case "ellps": $this->ellps = trim( $paramVal );break;case "a": $this->a = floatval( $paramVal );break; // semi-major radiuscase "b": $this->b = floatval( $paramVal );break; // semi-minor radius// DGR 2007-11-20case "rf": $this->rf = floatval( paramVal );break; // inverse flattening rf= a/(a-b)case "lat_0": $this->lat0 = $paramVal * Proj4php::$common->D2R;break; // phi0, central latitudecase "lat_1": $this->lat1 = $paramVal * Proj4php::$common->D2R;break; //standard parallel 1case "lat_2": $this->lat2 = $paramVal * Proj4php::$common->D2R;break; //standard parallel 2case "lat_ts": $this->lat_ts = $paramVal * Proj4php::$common->D2R;break; // used in merc and eqccase "lon_0": $this->long0 = $paramVal * Proj4php::$common->D2R;break; // lam0, central longitudecase "alpha": $this->alpha = floatval( $paramVal ) * Proj4php::$common->D2R;break; //for somerc projectioncase "lonc": $this->longc = paramVal * Proj4php::$common->D2R;break; //for somerc projectioncase "x_0": $this->x0 = floatval( $paramVal );break; // false eastingcase "y_0": $this->y0 = floatval( $paramVal );break; // false northingcase "k_0": $this->k0 = floatval( $paramVal );break; // projection scale factorcase "k": $this->k0 = floatval( $paramVal );break; // both forms returnedcase "r_a": $this->R_A = true;break; // sphere--area of ellipsoidcase "zone": $this->zone = intval( $paramVal, 10 );break; // UTM Zonecase "south": $this->utmSouth = true;break; // UTM north/southcase "towgs84": $this->datum_params = explode( ",", $paramVal );break;case "to_meter": $this->to_meter = floatval( $paramVal );break; // cartesian scalingcase "from_greenwich": $this->from_greenwich = $paramVal * Proj4php::$common->D2R;break;// DGR 2008-07-09 : if pm is not a well-known prime meridian take// the value instead of 0.0, then convert to radianscase "pm": $paramVal = trim( $paramVal );$this->from_greenwich = Proj4php::$primeMeridian[$paramVal] ? Proj4php::$primeMeridian[$paramVal] : floatval( $paramVal );$this->from_greenwich *= Proj4php::$common->D2R;break;// DGR 2010-11-12: axiscase "axis": $paramVal = trim( $paramVal );$legalAxis = "ewnsud";if( strlen( paramVal ) == 3 &&strpos( $legalAxis, substr( $paramVal, 0, 1 ) ) !== false &&strpos( $legalAxis, substr( $paramVal, 1, 1 ) ) !== false &&strpos( $legalAxis, substr( $paramVal, 2, 1 ) ) !== false ) {$this->axis = $paramVal;} //FIXME: be silent ?break;case "no_defs": break;default: //alert("Unrecognized parameter: " . paramName);} // switch()} // for paramArray$this->deriveConstants();}/*** Function: deriveConstants* Sets several derived constant values and initialization of datum and ellipse parameters.**/public function deriveConstants() {if( isset( $this->nagrids ) && $this->nagrids == '@null' )$this->datumCode = 'none';if( isset( $this->datumCode ) && $this->datumCode != 'none' ) {$datumDef = Proj4php::$datum[$this->datumCode];if( is_array($datumDef ) ) {$this->datum_params = array_key_exists( 'towgs84', $datumDef ) ? explode( ',', $datumDef['towgs84'] ) : null;$this->ellps = $datumDef['ellipse'];$this->datumName = array_key_exists( 'datumName', $datumDef ) ? $datumDef['datumName'] : $this->datumCode;}}if( !isset( $this->a ) ) { // do we have an ellipsoid?if( !isset( $this->ellps ) || strlen( $this->ellps ) == 0 || !array_key_exists( $this->ellps, Proj4php::$ellipsoid ) )$ellipse = Proj4php::$ellipsoid['WGS84'];else {$ellipse = Proj4php::$ellipsoid[$this->ellps];}Proj4php::extend( $this, $ellipse );}if( isset( $this->rf ) && !isset( $this->b ) )$this->b = (1.0 - 1.0 / $this->rf) * $this->a;if ( (isset($this->rf) && $this->rf === 0) || abs($this->a - $this->b) < Proj4php::$common->EPSLN) {$this->sphere = true;$this->b = $this->a;}$this->a2 = $this->a * $this->a; // used in geocentric$this->b2 = $this->b * $this->b; // used in geocentric$this->es = ($this->a2 - $this->b2) / $this->a2; // e ^ 2$this->e = sqrt( $this->es ); // eccentricityif( isset( $this->R_A ) ) {$this->a *= 1. - $this->es * (Proj4php::$common->SIXTH + $this->es * (Proj4php::$common->RA4 + $this->es * Proj4php::$common->RA6));$this->a2 = $this->a * $this->a;$this->b2 = $this->b * $this->b;$this->es = 0.0;}$this->ep2 = ($this->a2 - $this->b2) / $this->b2; // used in geocentricif( !isset( $this->k0 ) )$this->k0 = 1.0; //default value//DGR 2010-11-12: axisif( !isset( $this->axis ) ) {$this->axis = "enu";}$this->datum = new Proj4phpDatum( $this );}}