From 63633da4997dfd9a1bf134e78bed1c11dde13db8 Mon Sep 17 00:00:00 2001
From: Jon Atkins 
Date: Fri, 6 Jun 2014 21:17:00 +0100
Subject: [PATCH] phpcoord - osgb to lat/lng conversion, from
 http://www.jstott.me.uk/phpcoord/
---
 .../plugin-rpc/tfl/phpcoord/phpcoord-2.3.php  | 773 ++++++++++++++++++
 .../plugin-rpc/tfl/phpcoord/readme-2.3.txt    |  60 ++
 website/plugin-rpc/tfl/phpcoord/test-2.3.php  | 206 +++++
 3 files changed, 1039 insertions(+)
 create mode 100644 website/plugin-rpc/tfl/phpcoord/phpcoord-2.3.php
 create mode 100644 website/plugin-rpc/tfl/phpcoord/readme-2.3.txt
 create mode 100644 website/plugin-rpc/tfl/phpcoord/test-2.3.php
diff --git a/website/plugin-rpc/tfl/phpcoord/phpcoord-2.3.php b/website/plugin-rpc/tfl/phpcoord/phpcoord-2.3.php
new file mode 100644
index 00000000..b69d21af
--- /dev/null
+++ b/website/plugin-rpc/tfl/phpcoord/phpcoord-2.3.php
@@ -0,0 +1,773 @@
+toSixFigureString() so that the eastings and northings
+  //    are rounded rather than floored.
+  // 2.2 - 11 Feb 2006
+  //  - Used different algorithm for calculating distance between latitudes
+  //    and longitudes - fixes a number of problems with distance calculations
+  // 2.1 - 22 Dec 2005
+  //  - Added getOSRefFromSixFigureReference function
+  // 2.0 - 21 Dec 2005
+  //  - Completely different object design - conversion functions now through
+  //    objects rather than static functions
+  //  - Updated comments and documentation
+  // 1.1 - 11 Sep 2005
+  //  - Added OSGB36/WGS84 data conversions
+  // 1.0 - 11 Aug 2005
+  //  - Initial version
+  //--------------------------------------------------------------------------
+
+
+  // ================================================================== LatLng
+
+  class LatLng {
+
+    var $lat;
+    var $lng;
+
+
+    /**
+     * Create a new LatLng object from the given latitude and longitude
+     *
+     * @param lat latitude
+     * @param lng longitude
+     */
+    function LatLng($lat, $lng) {
+      $this->lat = $lat;
+      $this->lng = $lng;
+    }
+
+
+    /**
+     * Return a string representation of this LatLng object
+     *
+     * @return a string representation of this LatLng object
+     */
+    function toString() {
+      return "(" . $this->lat . ", " . $this->lng . ")";
+    }
+    
+    
+    /**
+     * Calculate the surface distance between this LatLng object and the one
+     * passed in as a parameter.
+     *
+     * @param to a LatLng object to measure the surface distance to
+     * @return the surface distance
+     */
+    function distance($to) {
+      $er = 6366.707;
+
+      $latFrom = deg2rad($this->lat);
+      $latTo   = deg2rad($to->lat);
+      $lngFrom = deg2rad($this->lng);
+      $lngTo   = deg2rad($to->lng);
+
+      $x1 = $er * cos($lngFrom) * sin($latFrom);
+      $y1 = $er * sin($lngFrom) * sin($latFrom);
+      $z1 = $er * cos($latFrom);
+
+      $x2 = $er * cos($lngTo) * sin($latTo);
+      $y2 = $er * sin($lngTo) * sin($latTo);
+      $z2 = $er * cos($latTo);
+
+      $d = acos(sin($latFrom)*sin($latTo) + cos($latFrom)*cos($latTo)*cos($lngTo-$lngFrom)) * $er;
+      
+      return $d;
+    }
+
+    
+    /**
+     * Convert this LatLng object from OSGB36 datum to WGS84 datum.
+     */
+    function OSGB36ToWGS84() {
+      $airy1830 = new RefEll(6377563.396, 6356256.909);
+      $a        = $airy1830->maj;
+      $b        = $airy1830->min;
+      $eSquared = $airy1830->ecc;
+      $phi = deg2rad($this->lat);
+      $lambda = deg2rad($this->lng);
+      $v = $a / (sqrt(1 - $eSquared * sinSquared($phi)));
+      $H = 0; // height
+      $x = ($v + $H) * cos($phi) * cos($lambda);
+      $y = ($v + $H) * cos($phi) * sin($lambda);
+      $z = ((1 - $eSquared) * $v + $H) * sin($phi);
+
+      $tx =        446.448;
+      $ty =       -124.157;
+      $tz =        542.060;
+      $s  =         -0.0000204894;
+      $rx = deg2rad( 0.00004172222);
+      $ry = deg2rad( 0.00006861111);
+      $rz = deg2rad( 0.00023391666);
+
+      $xB = $tx + ($x * (1 + $s)) + (-$rx * $y)     + ($ry * $z);
+      $yB = $ty + ($rz * $x)      + ($y * (1 + $s)) + (-$rx * $z);
+      $zB = $tz + (-$ry * $x)     + ($rx * $y)      + ($z * (1 + $s));
+
+      $wgs84 = new RefEll(6378137.000, 6356752.3141);
+      $a        = $wgs84->maj;
+      $b        = $wgs84->min;
+      $eSquared = $wgs84->ecc;
+
+      $lambdaB = rad2deg(atan($yB / $xB));
+      $p = sqrt(($xB * $xB) + ($yB * $yB));
+      $phiN = atan($zB / ($p * (1 - $eSquared)));
+      for ($i = 1; $i < 10; $i++) {
+        $v = $a / (sqrt(1 - $eSquared * sinSquared($phiN)));
+        $phiN1 = atan(($zB + ($eSquared * $v * sin($phiN))) / $p);
+        $phiN = $phiN1;
+      }
+
+      $phiB = rad2deg($phiN);
+      
+      $this->lat = $phiB;
+      $this->lng = $lambdaB;
+    }
+    
+    
+    /**
+     * Convert this LatLng object from WGS84 datum to OSGB36 datum.
+     */
+    function WGS84ToOSGB36() {
+      $wgs84 = new RefEll(6378137.000, 6356752.3141);
+      $a        = $wgs84->maj;
+      $b        = $wgs84->min;
+      $eSquared = $wgs84->ecc;
+      $phi = deg2rad($this->lat);
+      $lambda = deg2rad($this->lng);
+      $v = $a / (sqrt(1 - $eSquared * sinSquared($phi)));
+      $H = 0; // height
+      $x = ($v + $H) * cos($phi) * cos($lambda);
+      $y = ($v + $H) * cos($phi) * sin($lambda);
+      $z = ((1 - $eSquared) * $v + $H) * sin($phi);
+
+      $tx =       -446.448;
+      $ty =        124.157;
+      $tz =       -542.060;
+      $s  =          0.0000204894;
+      $rx = deg2rad(-0.00004172222);
+      $ry = deg2rad(-0.00006861111);
+      $rz = deg2rad(-0.00023391666);
+
+      $xB = $tx + ($x * (1 + $s)) + (-$rx * $y)     + ($ry * $z);
+      $yB = $ty + ($rz * $x)      + ($y * (1 + $s)) + (-$rx * $z);
+      $zB = $tz + (-$ry * $x)     + ($rx * $y)      + ($z * (1 + $s));
+
+      $airy1830 = new RefEll(6377563.396, 6356256.909);
+      $a        = $airy1830->maj;
+      $b        = $airy1830->min;
+      $eSquared = $airy1830->ecc;
+
+      $lambdaB = rad2deg(atan($yB / $xB));
+      $p = sqrt(($xB * $xB) + ($yB * $yB));
+      $phiN = atan($zB / ($p * (1 - $eSquared)));
+      for ($i = 1; $i < 10; $i++) {
+        $v = $a / (sqrt(1 - $eSquared * sinSquared($phiN)));
+        $phiN1 = atan(($zB + ($eSquared * $v * sin($phiN))) / $p);
+        $phiN = $phiN1;
+      }
+ 
+      $phiB = rad2deg($phiN);
+      
+      $this->lat = $phiB;
+      $this->lng = $lambdaB;
+    }
+    
+    
+    /**
+     * Convert this LatLng object into an OSGB grid reference. Note that this
+     * function does not take into account the bounds of the OSGB grid -
+     * beyond the bounds of the OSGB grid, the resulting OSRef object has no
+     * meaning
+     *
+     * @return the converted OSGB grid reference
+     */
+    function toOSRef() {
+      $airy1830 = new RefEll(6377563.396, 6356256.909);
+      $OSGB_F0  = 0.9996012717;
+      $N0       = -100000.0;
+      $E0       = 400000.0;
+      $phi0     = deg2rad(49.0);
+      $lambda0  = deg2rad(-2.0);
+      $a        = $airy1830->maj;
+      $b        = $airy1830->min;
+      $eSquared = $airy1830->ecc;
+      $phi = deg2rad($this->lat);
+      $lambda = deg2rad($this->lng);
+      $E = 0.0;
+      $N = 0.0;
+      $n = ($a - $b) / ($a + $b);
+      $v = $a * $OSGB_F0 * pow(1.0 - $eSquared * sinSquared($phi), -0.5);
+      $rho =
+        $a * $OSGB_F0 * (1.0 - $eSquared) * pow(1.0 - $eSquared * sinSquared($phi), -1.5);
+      $etaSquared = ($v / $rho) - 1.0;
+      $M =
+        ($b * $OSGB_F0)
+          * (((1 + $n + ((5.0 / 4.0) * $n * $n) + ((5.0 / 4.0) * $n * $n * $n))
+            * ($phi - $phi0))
+            - (((3 * $n) + (3 * $n * $n) + ((21.0 / 8.0) * $n * $n * $n))
+              * sin($phi - $phi0)
+              * cos($phi + $phi0))
+            + ((((15.0 / 8.0) * $n * $n) + ((15.0 / 8.0) * $n * $n * $n))
+              * sin(2.0 * ($phi - $phi0))
+              * cos(2.0 * ($phi + $phi0)))
+            - (((35.0 / 24.0) * $n * $n * $n)
+              * sin(3.0 * ($phi - $phi0))
+              * cos(3.0 * ($phi + $phi0))));
+      $I = $M + $N0;
+      $II = ($v / 2.0) * sin($phi) * cos($phi);
+      $III =
+        ($v / 24.0)
+          * sin($phi)
+          * pow(cos($phi), 3.0)
+          * (5.0 - tanSquared($phi) + (9.0 * $etaSquared));
+      $IIIA =
+        ($v / 720.0)
+          * sin($phi)
+          * pow(cos($phi), 5.0)
+          * (61.0 - (58.0 * tanSquared($phi)) + pow(tan($phi), 4.0));
+      $IV = $v * cos($phi);
+      $V = ($v / 6.0) * pow(cos($phi), 3.0) * (($v / $rho) - tanSquared($phi));
+      $VI =
+        ($v / 120.0)
+          * pow(cos($phi), 5.0)
+          * (5.0
+            - (18.0 * tanSquared($phi))
+            + (pow(tan($phi), 4.0))
+            + (14 * $etaSquared)
+            - (58 * tanSquared($phi) * $etaSquared));
+
+      $N =
+        $I
+          + ($II * pow($lambda - $lambda0, 2.0))
+          + ($III * pow($lambda - $lambda0, 4.0))
+          + ($IIIA * pow($lambda - $lambda0, 6.0));
+      $E =
+        $E0
+          + ($IV * ($lambda - $lambda0))
+          + ($V * pow($lambda - $lambda0, 3.0))
+          + ($VI * pow($lambda - $lambda0, 5.0));
+
+      return new OSRef($E, $N);
+    }
+    
+    
+    /**
+     * Convert a latitude and longitude to an UTM reference
+     *
+     * @return the converted UTM reference
+     */
+    function toUTMRef() {
+      $wgs84 = new RefEll(6378137, 6356752.314);
+      $UTM_F0   = 0.9996;
+      $a = $wgs84->maj;
+      $eSquared = $wgs84->ecc;
+      $longitude = $this->lng;
+      $latitude = $this->lat;
+
+      $latitudeRad = $latitude * (pi() / 180.0);
+      $longitudeRad = $longitude * (pi() / 180.0);
+      $longitudeZone = (int) (($longitude + 180.0) / 6.0) + 1;
+
+      // Special zone for Norway
+      if ($latitude >= 56.0
+        && $latitude < 64.0
+        && $longitude >= 3.0
+        && $longitude < 12.0) {
+        $longitudeZone = 32;
+      }
+
+      // Special zones for Svalbard
+      if ($latitude >= 72.0 && $latitude < 84.0) {
+        if ($longitude >= 0.0 && $longitude < 9.0) {
+          $longitudeZone = 31;
+        } else if ($longitude >= 9.0 && $longitude < 21.0) {
+          $longitudeZone = 33;
+        } else if ($longitude >= 21.0 && $longitude < 33.0) {
+          $longitudeZone = 35;
+        } else if ($longitude >= 33.0 && $longitude < 42.0) {
+          $longitudeZone = 37;
+        }
+      }
+
+      $longitudeOrigin = ($longitudeZone - 1) * 6 - 180 + 3;
+      $longitudeOriginRad = $longitudeOrigin * (pi() / 180.0);
+
+      $UTMZone = getUTMLatitudeZoneLetter($latitude);
+
+      $ePrimeSquared = ($eSquared) / (1 - $eSquared);
+
+      $n = $a / sqrt(1 - $eSquared * sin($latitudeRad) * sin($latitudeRad));
+      $t = tan($latitudeRad) * tan($latitudeRad);
+      $c = $ePrimeSquared * cos($latitudeRad) * cos($latitudeRad);
+      $A = cos($latitudeRad) * ($longitudeRad - $longitudeOriginRad);
+
+      $M =
+        $a
+          * ((1
+            - $eSquared / 4
+            - 3 * $eSquared * $eSquared / 64
+            - 5 * $eSquared * $eSquared * $eSquared / 256)
+            * $latitudeRad
+            - (3 * $eSquared / 8
+              + 3 * $eSquared * $eSquared / 32
+              + 45 * $eSquared * $eSquared * $eSquared / 1024)
+              * sin(2 * $latitudeRad)
+            + (15 * $eSquared * $eSquared / 256
+              + 45 * $eSquared * $eSquared * $eSquared / 1024)
+              * sin(4 * $latitudeRad)
+            - (35 * $eSquared * $eSquared * $eSquared / 3072)
+              * sin(6 * $latitudeRad));
+
+      $UTMEasting =
+        (double) ($UTM_F0
+          * $n
+          * ($A
+            + (1 - $t + $c) * pow($A, 3.0) / 6
+            + (5 - 18 * $t + $t * $t + 72 * $c - 58 * $ePrimeSquared)
+              * pow($A, 5.0)
+              / 120)
+          + 500000.0);
+
+      $UTMNorthing =
+        (double) ($UTM_F0
+          * ($M
+            + $n
+              * tan($latitudeRad)
+              * ($A * $A / 2
+                + (5 - $t + (9 * $c) + (4 * $c * $c)) * pow($A, 4.0) / 24
+                + (61 - (58 * $t) + ($t * $t) + (600 * $c) - (330 * $ePrimeSquared))
+                  * pow($A, 6.0)
+                  / 720)));
+
+      // Adjust for the southern hemisphere
+      if ($latitude < 0) {
+        $UTMNorthing += 10000000.0;
+      }
+
+      return new UTMRef($UTMEasting, $UTMNorthing, $UTMZone, $longitudeZone);
+    }    
+  }
+
+
+  // =================================================================== OSRef
+
+  // References given with OSRef are accurate to 1m.
+  class OSRef {
+
+    var $easting;
+    var $northing;
+
+
+    /**
+     * Create a new OSRef object representing an OSGB grid reference. Note
+     * that the parameters for this constructor require eastings and
+     * northings with 1m accuracy and need to be absolute with respect to
+     * the whole of the British Grid. For example, to create an OSRef
+     * object from the six-figure grid reference TG514131, the easting would
+     * be 651400 and the northing would be 313100.
+     * 
+     * Grid references with accuracy greater than 1m can be represented
+     * using floating point values for the easting and northing. For example,
+     * a value representing an easting or northing accurate to 1mm would be
+     * given as 651400.0001.
+     *
+     * @param easting the easting of the reference (with 1m accuracy)
+     * @param northing the northing of the reference (with 1m accuracy)
+     */
+    function OSRef($easting, $northing) {
+      $this->easting  = $easting;
+      $this->northing = $northing;
+    }
+
+
+    /**
+     * Convert this grid reference into a string showing the exact values
+     * of the easting and northing.
+     *
+     * @return
+     */
+    function toString() {
+      return "(" . $this->easting . ", " . $this->northing . ")";
+    }
+
+
+    /**
+     * Convert this grid reference into a string using a standard six-figure
+     * grid reference including the two-character designation for the 100km
+     * square. e.g. TG514131. 
+     *
+     * @return
+     */
+    function toSixFigureString() {
+      $hundredkmE = floor($this->easting / 100000);
+      $hundredkmN = floor($this->northing / 100000);
+      $firstLetter = "";
+      if ($hundredkmN < 5) {
+        if ($hundredkmE < 5) {
+          $firstLetter = "S";
+        } else {
+          $firstLetter = "T";
+        }
+      } else if ($hundredkmN < 10) {
+        if ($hundredkmE < 5) {
+          $firstLetter = "N";
+        } else {
+          $firstLetter = "O";
+        }
+      } else {
+        $firstLetter = "H";
+      }
+
+      $secondLetter = "";
+      $index = 65 + ((4 - ($hundredkmN % 5)) * 5) + ($hundredkmE % 5);
+      $ti = $index;
+      if ($index >= 73) $index++;
+      $secondLetter = chr($index);
+
+      $e = round(($this->easting - (100000 * $hundredkmE)) / 100);
+      $n = round(($this->northing - (100000 * $hundredkmN)) / 100);
+
+      return sprintf("%s%s%03d%03d", $firstLetter, $secondLetter, $e, $n);
+    }
+
+
+    /**
+     * Convert this grid reference into a latitude and longitude
+     *
+     * @return
+     */
+    function toLatLng() {
+      $airy1830 = new RefEll(6377563.396, 6356256.909);
+      $OSGB_F0  = 0.9996012717;
+      $N0       = -100000.0;
+      $E0       = 400000.0;
+      $phi0     = deg2rad(49.0);
+      $lambda0  = deg2rad(-2.0);
+      $a        = $airy1830->maj;
+      $b        = $airy1830->min;
+      $eSquared = $airy1830->ecc;
+      $phi      = 0.0;
+      $lambda   = 0.0;
+      $E        = $this->easting;
+      $N        = $this->northing;
+      $n        = ($a - $b) / ($a + $b);
+      $M        = 0.0;
+      $phiPrime = (($N - $N0) / ($a * $OSGB_F0)) + $phi0;
+      do {
+        $M =
+          ($b * $OSGB_F0)
+            * (((1 + $n + ((5.0 / 4.0) * $n * $n) + ((5.0 / 4.0) * $n * $n * $n))
+              * ($phiPrime - $phi0))
+              - (((3 * $n) + (3 * $n * $n) + ((21.0 / 8.0) * $n * $n * $n))
+                * sin($phiPrime - $phi0)
+                * cos($phiPrime + $phi0))
+              + ((((15.0 / 8.0) * $n * $n) + ((15.0 / 8.0) * $n * $n * $n))
+                * sin(2.0 * ($phiPrime - $phi0))
+                * cos(2.0 * ($phiPrime + $phi0)))
+              - (((35.0 / 24.0) * $n * $n * $n)
+                * sin(3.0 * ($phiPrime - $phi0))
+                * cos(3.0 * ($phiPrime + $phi0))));
+        $phiPrime += ($N - $N0 - $M) / ($a * $OSGB_F0);
+      } while (($N - $N0 - $M) >= 0.001);
+      $v = $a * $OSGB_F0 * pow(1.0 - $eSquared * sinSquared($phiPrime), -0.5);
+      $rho =
+        $a
+          * $OSGB_F0
+          * (1.0 - $eSquared)
+          * pow(1.0 - $eSquared * sinSquared($phiPrime), -1.5);
+      $etaSquared = ($v / $rho) - 1.0;
+      $VII = tan($phiPrime) / (2 * $rho * $v);
+      $VIII =
+        (tan($phiPrime) / (24.0 * $rho * pow($v, 3.0)))
+          * (5.0
+            + (3.0 * tanSquared($phiPrime))
+            + $etaSquared
+            - (9.0 * tanSquared($phiPrime) * $etaSquared));
+      $IX =
+        (tan($phiPrime) / (720.0 * $rho * pow($v, 5.0)))
+          * (61.0
+            + (90.0 * tanSquared($phiPrime))
+            + (45.0 * tanSquared($phiPrime) * tanSquared($phiPrime)));
+      $X = sec($phiPrime) / $v;
+      $XI =
+        (sec($phiPrime) / (6.0 * $v * $v * $v))
+          * (($v / $rho) + (2 * tanSquared($phiPrime)));
+      $XII =
+        (sec($phiPrime) / (120.0 * pow($v, 5.0)))
+          * (5.0
+            + (28.0 * tanSquared($phiPrime))
+            + (24.0 * tanSquared($phiPrime) * tanSquared($phiPrime)));
+      $XIIA =
+        (sec($phiPrime) / (5040.0 * pow($v, 7.0)))
+          * (61.0
+            + (662.0 * tanSquared($phiPrime))
+            + (1320.0 * tanSquared($phiPrime) * tanSquared($phiPrime))
+            + (720.0
+              * tanSquared($phiPrime)
+              * tanSquared($phiPrime)
+              * tanSquared($phiPrime)));
+      $phi =
+        $phiPrime
+          - ($VII * pow($E - $E0, 2.0))
+          + ($VIII * pow($E - $E0, 4.0))
+          - ($IX * pow($E - $E0, 6.0));
+      $lambda =
+        $lambda0
+          + ($X * ($E - $E0))
+          - ($XI * pow($E - $E0, 3.0))
+          + ($XII * pow($E - $E0, 5.0))
+          - ($XIIA * pow($E - $E0, 7.0));
+ 
+      return new LatLng(rad2deg($phi), rad2deg($lambda));
+    }
+  }
+
+
+  // ================================================================== UTMRef
+
+  class UTMRef {
+
+    var $easting;
+    var $northing;
+    var $latZone;
+    var $lngZone;
+
+
+    /**
+     * Create a new object representing a UTM reference.
+     *
+     * @param easting
+     * @param northing
+     * @param latZone
+     * @param lngZone
+     */
+    function UTMRef($easting, $northing, $latZone, $lngZone) {
+      $this->easting  = $easting;
+      $this->northing = $northing;
+      $this->latZone  = $latZone;
+      $this->lngZone  = $lngZone;
+    }
+
+
+    /**
+     * Return a string representation of this UTM reference
+     *
+     * @return
+     */
+    function toString() {
+      return $this->lngZone . $this->latZone . " " .
+             $this->easting . " " . $this->northing;
+    }
+    
+    
+    /**
+     * Convert this UTM reference to a latitude and longitude
+     *
+     * @return the converted latitude and longitude
+     */
+    function toLatLng() {
+      $wgs84 = new RefEll(6378137, 6356752.314);
+      $UTM_F0   = 0.9996;
+      $a = $wgs84->maj;
+      $eSquared = $wgs84->ecc;
+      $ePrimeSquared = $eSquared / (1.0 - $eSquared);
+      $e1 = (1 - sqrt(1 - $eSquared)) / (1 + sqrt(1 - $eSquared));
+      $x = $this->easting - 500000.0;;
+      $y = $this->northing;
+      $zoneNumber = $this->lngZone;
+      $zoneLetter = $this->latZone;
+
+      $longitudeOrigin = ($zoneNumber - 1.0) * 6.0 - 180.0 + 3.0;
+
+      // Correct y for southern hemisphere
+      if ((ord($zoneLetter) - ord("N")) < 0) {
+        $y -= 10000000.0;
+      }
+
+      $m = $y / $UTM_F0;
+      $mu =
+        $m
+          / ($a
+            * (1.0
+              - $eSquared / 4.0
+              - 3.0 * $eSquared * $eSquared / 64.0
+              - 5.0
+                * pow($eSquared, 3.0)
+                / 256.0));
+
+      $phi1Rad =
+        $mu
+          + (3.0 * $e1 / 2.0 - 27.0 * pow($e1, 3.0) / 32.0) * sin(2.0 * $mu)
+          + (21.0 * $e1 * $e1 / 16.0 - 55.0 * pow($e1, 4.0) / 32.0)
+            * sin(4.0 * $mu)
+          + (151.0 * pow($e1, 3.0) / 96.0) * sin(6.0 * $mu);
+
+      $n =
+        $a
+          / sqrt(1.0 - $eSquared * sin($phi1Rad) * sin($phi1Rad));
+      $t = tan($phi1Rad) * tan($phi1Rad);
+      $c = $ePrimeSquared * cos($phi1Rad) * cos($phi1Rad);
+      $r =
+        $a
+          * (1.0 - $eSquared)
+          / pow(
+            1.0 - $eSquared * sin($phi1Rad) * sin($phi1Rad),
+            1.5);
+      $d = $x / ($n * $UTM_F0);
+
+      $latitude = (
+        $phi1Rad
+          - ($n * tan($phi1Rad) / $r)
+            * ($d * $d / 2.0
+              - (5.0
+                + (3.0 * $t)
+                + (10.0 * $c)
+                - (4.0 * $c * $c)
+                - (9.0 * $ePrimeSquared))
+                * pow($d, 4.0)
+                / 24.0
+              + (61.0
+                + (90.0 * $t)
+                + (298.0 * $c)
+                + (45.0 * $t * $t)
+                - (252.0 * $ePrimeSquared)
+                - (3.0 * $c * $c))
+                * pow($d, 6.0)
+                / 720.0)) * (180.0 / pi());
+
+      $longitude = $longitudeOrigin + (
+        ($d
+          - (1.0 + 2.0 * $t + $c) * pow($d, 3.0) / 6.0
+          + (5.0
+            - (2.0 * $c)
+            + (28.0 * $t)
+            - (3.0 * $c * $c)
+            + (8.0 * $ePrimeSquared)
+            + (24.0 * $t * $t))
+            * pow($d, 5.0)
+            / 120.0)
+          / cos($phi1Rad)) * (180.0 / pi());
+
+      return new LatLng($latitude, $longitude);
+    }   
+  }
+
+
+  // ================================================================== RefEll
+
+  class RefEll {
+
+    var $maj;
+    var $min;
+    var $ecc;
+
+
+    /**
+     * Create a new RefEll object to represent a reference ellipsoid
+     *
+     * @param maj the major axis
+     * @param min the minor axis
+     */
+    function RefEll($maj, $min) {
+      $this->maj = $maj;
+      $this->min = $min;
+      $this->ecc = (($maj * $maj) - ($min * $min)) / ($maj * $maj);
+    }
+  }
+
+
+  // ================================================== Mathematical Functions
+
+  function sinSquared($x) {
+    return sin($x) * sin($x);
+  }
+
+  function cosSquared($x) {
+    return cos($x) * cos($x);
+  }
+
+  function tanSquared($x) {
+    return tan($x) * tan($x);
+  }
+
+  function sec($x) {
+    return 1.0 / cos($x);
+  }
+  
+  
+  /**
+   * Take a string formatted as a six-figure OS grid reference (e.g.
+   * "TG514131") and return a reference to an OSRef object that represents
+   * that grid reference. The first character must be H, N, S, O or T.
+   * The second character can be any uppercase character from A through Z
+   * excluding I.
+   *
+   * @param ref
+   * @return
+   * @since 2.1
+   */
+  function getOSRefFromSixFigureReference($ref) {
+    $char1 = substr($ref, 0, 1);
+    $char2 = substr($ref, 1, 1);
+    $east  = substr($ref, 2, 3) * 100;
+    $north = substr($ref, 5, 3) * 100;
+    if ($char1 == 'H') {
+      $north += 1000000;
+    } else if ($char1 == 'N') {
+      $north += 500000;
+    } else if ($char1 == 'O') {
+      $north += 500000;
+      $east  += 500000;
+    } else if ($char1 == 'T') {
+      $east += 500000;
+    }
+    $char2ord = ord($char2);
+    if ($char2ord > 73) $char2ord--; // Adjust for no I
+    $nx = (($char2ord - 65) % 5) * 100000;
+    $ny = (4 - floor(($char2ord - 65) / 5)) * 100000;
+    return new OSRef($east + $nx, $north + $ny);
+  }
+  
+    
+  /**
+   *  Work out the UTM latitude zone from the latitude
+   *
+   * @param latitude
+   * @return
+   */
+  function getUTMLatitudeZoneLetter($latitude) {
+    if ((84 >= $latitude) && ($latitude >= 72)) return "X";
+    else if (( 72 > $latitude) && ($latitude >=  64)) return "W";
+    else if (( 64 > $latitude) && ($latitude >=  56)) return "V";
+    else if (( 56 > $latitude) && ($latitude >=  48)) return "U";
+    else if (( 48 > $latitude) && ($latitude >=  40)) return "T";
+    else if (( 40 > $latitude) && ($latitude >=  32)) return "S";
+    else if (( 32 > $latitude) && ($latitude >=  24)) return "R";
+    else if (( 24 > $latitude) && ($latitude >=  16)) return "Q";
+    else if (( 16 > $latitude) && ($latitude >=   8)) return "P";
+    else if ((  8 > $latitude) && ($latitude >=   0)) return "N";
+    else if ((  0 > $latitude) && ($latitude >=  -8)) return "M";
+    else if (( -8 > $latitude) && ($latitude >= -16)) return "L";
+    else if ((-16 > $latitude) && ($latitude >= -24)) return "K";
+    else if ((-24 > $latitude) && ($latitude >= -32)) return "J";
+    else if ((-32 > $latitude) && ($latitude >= -40)) return "H";
+    else if ((-40 > $latitude) && ($latitude >= -48)) return "G";
+    else if ((-48 > $latitude) && ($latitude >= -56)) return "F";
+    else if ((-56 > $latitude) && ($latitude >= -64)) return "E";
+    else if ((-64 > $latitude) && ($latitude >= -72)) return "D";
+    else if ((-72 > $latitude) && ($latitude >= -80)) return "C";
+    else return 'Z';
+  }
+
+?>
diff --git a/website/plugin-rpc/tfl/phpcoord/readme-2.3.txt b/website/plugin-rpc/tfl/phpcoord/readme-2.3.txt
new file mode 100644
index 00000000..25aca348
--- /dev/null
+++ b/website/plugin-rpc/tfl/phpcoord/readme-2.3.txt
@@ -0,0 +1,60 @@
+--------------------------------------------------------------------------
+ PHPcoord
+ readme.txt
+ 
+ (c) 2005 Jonathan Stott
+ 
+ Created on 11-Aug-2005
+ 
+ 2.3 - 24 Aug 2006
+  - Changed OSRef->toSixFigureString() so that the eastings and northings
+    are rounded rather than floored.
+ 2.2 - 11 Feb 2006
+   - Used different algorithm for calculating distance between latitudes
+     and longitudes - fixes a number of problems with distance calculations
+ 2.1 - 22 Dec 2005
+  - Added getOSRefFromSixFigureReference function
+ 2.0 - 21 Dec 2005
+  - Completely different object design - conversion functions now through
+    objects rather than static functions
+  - Updated comments and documentation
+ 1.1 - 11 Sep 2005
+  - Added WGS84/OSGB36 conversions
+ 1.0 - 11 Aug 2005
+  - Initial version
+--------------------------------------------------------------------------
+
+PHPcoord is a PHP script that provides functions for handling various
+co-ordinate systems and converting between them. Currently, OSGB (Ordnance
+Survey of Great Britain) grid references, UTM (Universal Transverse
+Mercator) references and latitude/longitude are supported. A function is 
+also provided to find the surface distance between two points of latitude
+and longitude.
+
+When using the OSGB conversions, the majority of applications use the
+WGS84 datum rather than the OSGB36 datum. Conversions between the two
+data were added in v1.1 - the conversions should be accurate to within
+5m or so. If accuracy is not important (i.e. to within 200m or so),
+then it isn't necessary to perform the conversions.
+
+Examples of how to use the functions provided in phpcoord.php can be
+found in the test.php script.
+
+See http://www.jstott.me.uk/phpcoord/ for latest releases and information.
+
+
+DISCLAIMER
+
+Accuracy of the co-ordinate conversions contained within the PHPcoord
+package is not guaranteed. Use of the conversions is entirely at your
+own risk and I cannot be held responsible for any consequences of
+errors created by the conversions. I do not recommend using the package
+for mission-critical applications.
+
+
+LICENSING
+
+This software product is available under the GNU General Public License
+(GPL). Terms of the GPL can be read at http://www.jstott.me.uk/gpl/.
+Any commercial use requires the purchase of a license - contact me at
+phpcoord@jstott.me.uk for details.
diff --git a/website/plugin-rpc/tfl/phpcoord/test-2.3.php b/website/plugin-rpc/tfl/phpcoord/test-2.3.php
new file mode 100644
index 00000000..4cbc11bf
--- /dev/null
+++ b/website/plugin-rpc/tfl/phpcoord/test-2.3.php
@@ -0,0 +1,206 @@
+toSixFigureString() so that the eastings and northings
+  //    are rounded rather than floored.
+  // 2.2 - 11 Feb 2006
+  //  - Used different algorithm for calculating distance between latitudes
+  //    and longitudes - fixes a number of problems with distance calculations
+  // 2.1 - 22 Dec 2005
+  //  - Added getOSRefFromSixFigureReference function
+  // 2.0 - 21 Dec 2005
+  //  - Completely different object design - conversion functions now through
+  //    objects rather than static functions
+  //  - Updated comments and documentation
+  // 1.1 - 11 Sep 2005
+  //  - Added OSGB36/WGS84 data conversions
+  // 1.0 - 11 Aug 2005
+  //  - Initial version
+  //--------------------------------------------------------------------------
+
+
+  require_once("phpcoord-2.3.php");
+?>
+
+
+
+  
+    phpcoord Test Script
+  
+
+  
+
+    phpcoord Test Script
+
+    Calculate Surface Distance between two Latitudes/Longitudes
+
+    
+      The LatLngDistance function takes two latitudes/longitudes and calculates
+      the surface distance between the two in kilometres:
+    
+
+    
+      
$lld1 = new LatLng(40.718119, -73.995667); // New York
+echo "New York Lat/Long: " . $lld1->toString() . "<br />";
+$lld2 = new LatLng(51.499981, -0.125313);  // London
+$d = $lld1->distance($lld2);
+echo "Surface Distance between New York and London: " . $d . "km";
+
+      toString() . "
";
+        $lld2 = new LatLng(51.499981, -0.125313);  // London
+        echo "London Lat/Long: " . $lld2->toString() . "
";
+        $d = $lld1->distance($lld2);
+        echo "Surface Distance between New York and London: " . $d . "km";
+      ?>
+    
+
+    Convert OS Grid Reference to Latitude/Longitude
+
+    
+      Note that the OSGB-Latitude/Longitude conversions use the OSGB36 datum by default. The
+      majority of applications use the WGS84 datum, for which the appropriate conversions
+      need to be added. See the examples below to see the difference between the two data.
+    
+
+    
+      Using OSGB36 (convert an OSGB grid reference to a latitude and longitude using the OSGB36 datum):
+
+      
$os1 = new OSRef(651409.903, 313177.270);
+echo "OS Grid Reference: " . $os1->toString() . " - " . $os1->toSixFigureString() . "<br />";
+$ll1 = $os1->toLatLng();
+echo "Converted to Lat/Long: " . $ll1->toString();
+
+      toString() . " - " . $os1->toSixFigureString() . "
";
+        $ll1 = $os1->toLatLng();
+        echo "Converted to Lat/Long: " . $ll1->toString();
+      ?>
+    
+
+    
+      Using WGS84 (convert an OSGB grid reference to a latitude and longitude using the WGS84 datum):
+
+      
$os1w = new OSRef(651409.903, 313177.270);
+echo "OS Grid Reference: " . $os1w->toString() . " - " . $os1w->toSixFigureString() . "<br />";
+$l1w = $os1w->toLatLng();
+$l1w->OSGB36ToWGS84();
+echo "Converted to Lat/Long: " . $ll1w->toString();
+
+      toString() . " - " . $os1w->toSixFigureString() . "
";
+        $ll1w = $os1w->toLatLng();
+        $ll1w->OSGB36ToWGS84();
+        echo "Converted to Lat/Long: " . $ll1w->toString();
+      ?>
+    
+
+    Convert Latitude/Longitude to OS Grid Reference
+
+    
+      Note that the OSGB-Latitude/Longitude conversions use the OSGB36 datum by default. The
+      majority of applications use the WGS84 datum, for which the appropriate conversions
+      need to be added. See the examples below to see the difference between the two data.
+    
+
+    
+      Using OSGB36 (convert a latitude and longitude using the OSGB36 datum to an OSGB grid reference):
+
+      
$ll2 = new LatLng(52.657570301933, 1.7179215806451);
+echo "Latitude/Longitude: " . $ll2->toString() . "<br />";
+$os2 = $ll2->toOSRef();
+echo "Converted to OS Grid Ref: " . $os2->toString() . " - " . $os2->toSixFigureString();
+
+      toString() . "
";
+        $os2 = $ll2->toOSRef();
+        echo "Converted to OS Grid Ref: " . $os2->toString() . " - " . $os2->toSixFigureString();
+      ?>
+    
+
+    
+      Using WGS84 (convert a latitude and longitude using the WGS84 datum to an OSGB grid reference):
+
+      
$ll2w = new LatLng(52.657570301933, 1.7179215806451);
+echo "Latitude/Longitude: " . $ll2->toString() . "<br />";
+$ll2w->WGS84ToOSGB36();
+$os2w = $ll2w->toOSRef();
+echo "Converted to OS Grid Ref: " . $os2w->toString() . " - " . $os2w->toSixFigureString();
+
+      toString() . "
";
+        $ll2w->WGS84ToOSGB36();
+        $os2w = $ll2w->toOSRef();
+        echo "Converted to OS Grid Ref: " . $os2w->toString() . " - " . $os2w->toSixFigureString();
+      ?>
+    
+    
+    Convert Six-Figure OS Grid Reference String to an OSRef Object
+    
+    
+      To convert a string representing a six-figure OSGB grid reference:
+
+      
$os6 = "TG514131";
+echo "Six figure string: " . $os6 . "<br />";
+$os6x = getOSRefFromSixFigureReference($os6);
+echo "Converted to OS Grid Ref: " . $os6x->toString() . " - " . $os6x->toSixFigureString();
    
+      
+      ";
+        $os6x = getOSRefFromSixFigureReference($os6);
+        echo "Converted to OS Grid Ref: " . $os6x->toString() . " - " . $os6x->toSixFigureString();
+      ?>
+    
+
+    Convert UTM Reference to Latitude/Longitude
+
+    
+      
$utm1 = new UTMRef(456463.99, 3335334.05, "E", 12);
+echo "UTM Reference: " . $utm1->toString() . "<br />";
+$ll3 = $utm1->toLatLng();
+echo "Converted to Lat/Long: " . $ll3->toString();
+
+      toString() . "
";
+        $ll3 = $utm1->toLatLng();
+        echo "Converted to Lat/Long: " . $ll3->toString();
+      ?>
+    
+
+    Convert Latitude/Longitude to UTM Reference
+
+    
+      
$ll4 = new LatLng(-60.1167, -111.7833);
+echo "Latitude/Longitude: " . $ll4->toString() . "<br />";
+$utm2 = $ll4->toUTMRef();
+echo "Converted to UTM Ref: " . $utm2->toString() ;
+
+      toString() . "
";
+        $utm2 = $ll4->toUTMRef();
+        echo "Converted to UTM Ref: " . $utm2->toString() ;
+      ?>
+    
+
+    
+      (c) 2005, Jonathan Stott
+    
+
+  
+