<?php
    
// +----------------------------------------------------------------------+
    // | PHP version 4                                                        |
    // +----------------------------------------------------------------------+
    // | Copyright (c) 2003-2004 Michal Migurski                              |
    // +----------------------------------------------------------------------+
    // | This source file is subject to version 3.0 of the PHP license,       |
    // | that is bundled with this package in the file LICENSE, and is        |
    // | available through the world-wide-web at the following url:           |
    // | http://www.php.net/license/3_0.txt.                                  |
    // | If you did not receive a copy of the PHP license and are unable to   |
    // | obtain it through the world-wide-web, please send a note to          |
    // | license@php.net so we can mail you a copy immediately.               |
    // +----------------------------------------------------------------------+
    // | Author: Michal Migurski <mike@teczno.com>                            |
    // +----------------------------------------------------------------------+
    //
    // $Id: Linear.php,v 1.4 2004/11/24 23:47:35 migurski Exp $
    /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

    // theta: latitude (N/S)
    // lambda: longitude (E/W)
    // DON'T FORGET: deg2rad()
    
    
ob_start(); {
        
// I can't believe the PEAR library has white space in it.
        
require_once('Math/Matrix.php');
    } 
ob_end_clean();

   
/** Linear_Projection
    * calculates not a lot
    */
    
class Linear_Projection
    
{
        var 
$transform;
        var 
$inverse;
    
        function 
Linear_Projection()
        {
            
$this->initializeTransform();
        }
        
       
/** _GPSToMap
        * calculates map x,y coordinate from gps lat/long coordinates
        *
        * @param    lat     float       latitude, in radians
        * @param    lon     float       longitude, in radians
        *
        * @return   array   'x', 'y' elements, map coordinates
        */
        
function _GPSToMap($lat$lon)
        {
            
$vector = new Math_Matrix(array(array($lon$lat1)));
            
$vector->multiply($this->transform);

            return array(
'x' => $vector->getElement(00),
                         
'y' => $vector->getElement(01));
        }
    
       
/** _mapToGPS
        * calculates gps lat/long coordinate from map coordinates.
        *
        * @param    x       float       horizontal map position
        * @param    y       float       vertical map position
        *
        * @return   array   'lat', 'lon' elements, gps coordinates in radians
        */
        
function _mapToGPS($x$y)
        {
            
$vector = new Math_Matrix(array(array($x$y1)));
            
$vector->multiply($this->inverse);

            return array(
'lat' => $vector->getElement(01),
                         
'lon' => $vector->getElement(00));
        }
        
       
/** initializeTransform
        * initializes interal transformation matrices to the 3x3 identity
        * matrix, which should have no effect.
        */
        
function initializeTransform()
        {
            
$this->transform Math_Matrix::makeIdentity(3);
            
$this->inverse Math_Matrix::makeIdentity(3);
        }
        
       
/** addTransform
        * adds a transformation to the internal transformation matrices.
        * see http://bishopw.loni.ucla.edu/aiR5/2Drigidbody.html for an
        * explanation of 2D rigid body transformations using matrices.
        *
        * @param    0   string      type of transformation:
        *                           'rotate', 'rot': rotation
        *                           'translate', 'trans': translation
        *                           'scale': scale
        * @param    1,2 number      transformation parameters:
        *                           rotation: number of degrees, clockwise
        *                           translation: units to move x, y
        *                           scale: amount to scale x, y
        */
        
function addTransform()
        {
            
$matrix Math_Matrix::makeIdentity(3);

            if((
$args func_get_args()) && (count($args) > 1)) {
                switch(
$args[0]) {
                    case 
'rotate':
                    case 
'rot':
                        if(
is_numeric($args[1])) {
                            
$theta deg2rad($args[1]);
                            
$matrix->setElement(00cos($theta));
                            
$matrix->setElement(11cos($theta));
                            
$matrix->setElement(01sin($theta));
                            
$matrix->setElement(10, (-sin($theta)));
                        }
                        break;

                    case 
'translate':
                    case 
'trans':
                        if(
is_numeric($args[1]) && is_numeric($args[2])) {
                            
$x_transform $args[1];
                            
$y_transform $args[2];
                            
$matrix->setElement(20$x_transform);
                            
$matrix->setElement(21$y_transform);
                        }
                        break;

                    case 
'scale':
                        if(
is_numeric($args[1]) && is_numeric($args[2])) {
                            
$x_scale $args[1];
                            
$y_scale $args[2];
                            
$matrix->setElement(00$x_scale);
                            
$matrix->setElement(11$y_scale);
                        }
                        break;

                }
            }
            
            
$this->transform->multiply($matrix);
            
            
$matrix $this->transform;
            
$matrix->invert();
            
$this->inverse $matrix;
            
            
//echo "transform:\n", $this->transform->toString();
            //echo "inverse:\n", $this->inverse->toString();
        
}
        
       
/** project
        * project a point or series of points from GPS to a map.
        * modifies points by reference.
        *
        * @param    points      array   reference to a point or array of points.
        *                               a point is an array with two numeric elements, 'lat' and 'lon'.
        *                               $points['lat'] and $points['lon'] are latitude and longitude, in radians.
        *
        * @return   boolean     false on failure, true on success
        */
        
function project(&$points)
        {
            if (!
is_array($points)) {
                return 
false;
            }
            
            if (isset(
$points['lat']) && is_numeric($points['lat']) && isset($points['lon']) && is_numeric($points['lon'])) {
                
$projected $this->_GPSToMap($points['lat'], $points['lon']);
                
$points['x'] = $projected['x'];
                
$points['y'] = $projected['y'];
                return 
true;
            }

            foreach (
$points as $p => $point) {
                if (isset(
$point['lat']) && is_numeric($point['lat']) && isset($point['lon']) && is_numeric($point['lon'])) {
                    
$projected $this->_GPSToMap($point['lat'], $point['lon']);
                    
$points[$p]['x'] = $projected['x'];
                    
$points[$p]['y'] = $projected['y'];
                }
            }
            
            return 
true;
        }
        
       
/** unproject
        * unproject a point or series of points from a map to GPS.
        * modifies points by reference.
        *
        * @param    points      array   reference to a point or array of points.
        *                               a point is an array with two numeric elements, 'x' and 'y'.
        *                               $points['x'] and $points['y'] are cartesian points on a map.
        *
        * @return   boolean     false on failure, true on success
        */
        
function unproject(&$points)
        {
            if (!
is_array($points)) {
                return 
false;
            }
            
            if (isset(
$points['x']) && is_numeric($points['x']) && isset($points['y']) && is_numeric($points['y'])) {
                
$projected $this->_mapToGPS($points['x'], $points['y']);
                
$points['lat'] = $projected['lat'];
                
$points['lon'] = $projected['lon'];
                return 
true;
            }

            foreach (
$points as $p => $point) {
                if (isset(
$point['x']) && is_numeric($point['x']) && isset($point['y']) && is_numeric($point['y'])) {
                    
$unprojected $this->_mapToGPS($point['x'], $point['y']);
                    
$points[$p]['lat'] = $unprojected['lat'];
                    
$points[$p]['lon'] = $unprojected['lon'];
                }
            }
            
            return 
true;
        }
    }

?>