[proj4js] 02/06: Imported Upstream version 2.3.16+ds

Bas Couwenberg sebastic at debian.org
Mon Jan 23 21:45:33 UTC 2017


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository proj4js.

commit 7f09e8d348cb92bcc854e777e460f68b70fd1e7c
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Jan 23 22:33:13 2017 +0100

    Imported Upstream version 2.3.16+ds
---
 .gitignore                |    3 +-
 Gruntfile.js              |    3 +-
 README.md                 |    9 +-
 bower.json                |    2 +-
 component.json            |    2 +-
 dist/proj4-src.js         | 1336 +++++++++++++++++++++------------------------
 dist/test.html            |   11 -
 lib/Proj.js               |   54 +-
 lib/adjust_axis.js        |   15 +-
 lib/common/adjust_zone.js |   19 +
 lib/datum.js              |  413 +-------------
 lib/datumUtils.js         |  247 +++++++++
 lib/datum_transform.js    |   96 +---
 lib/deriveConstants.js    |   89 ++-
 lib/index.js              |    6 +-
 lib/parseCode.js          |   15 +-
 lib/projString.js         |   12 +-
 lib/projections/tmerc.js  |  142 +++--
 lib/projections/utm.js    |   11 +-
 lib/transform.js          |   60 +-
 lib/version-browser.js    |    1 +
 lib/version.js            |    1 +
 package.json              |   16 +-
 publish.sh                |    3 +-
 test/test.js              |    9 +-
 test/testData.js          |   84 ++-
 26 files changed, 1269 insertions(+), 1390 deletions(-)

diff --git a/.gitignore b/.gitignore
index 312583b..0f3e170 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ node_modules
 coverage
 projs.js
 .DS_STORE
-dist
\ No newline at end of file
+dist
+lib/version-browser.js
diff --git a/Gruntfile.js b/Gruntfile.js
index ebd05c0..c35d3c4 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -61,7 +61,8 @@ module.exports = function(grunt) {
             standalone: 'proj4'
           },
           alias: [
-            './projs:./includedProjections'
+            './projs:./includedProjections',
+            './lib/version-browser:./lib/version'
             ]
         }
       }
diff --git a/README.md b/README.md
index 3bb37fe..f4aeae1 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,6 @@ Depending on your preferences
 ```bash
 npm install proj4
 bower install proj4
-jam install proj4
 component install proj4js/proj4js
 ```
 
@@ -114,6 +113,14 @@ proj4.defs can also be used to define a named alias:
 proj4.defs('urn:x-ogc:def:crs:EPSG:4326', proj4.defs('EPSG:4326'));
 ```
 
+## TypeScript
+
+TypeScript implementation was added to the [DefinitelyTyped repository](https://github.com/DefinitelyTyped/DefinitelyTyped).
+
+```bash
+$ npm install --save @types/proj4
+```
+
 ## Developing
 to set up build tools make sure you have node and grunt-cli installed and then run `npm install`
 
diff --git a/bower.json b/bower.json
index 814d89c..2781321 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "proj4",
-  "version": "2.3.15-alpha",
+  "version": "2.3.16-alpha",
   "description": "Proj4js is a JavaScript library to transform point coordinates from one coordinate system to another, including datum transformations.",
   "homepage": "https://github.com/proj4js/proj4js",
   "main": "dist/proj4.js",
diff --git a/component.json b/component.json
index e600be3..d5a3a3b 100644
--- a/component.json
+++ b/component.json
@@ -1,6 +1,6 @@
 {
   "name": "proj4",
-  "version": "2.3.15-alpha",
+  "version": "2.3.16-alpha",
   "description": "Proj4js is a JavaScript library to transform point coordinates from one coordinate system to another, including datum transformations.",
   "repo": "proj4js/proj4js",
   "keywords": [
diff --git a/dist/proj4-src.js b/dist/proj4-src.js
index 24da1e7..865a89e 100644
--- a/dist/proj4-src.js
+++ b/dist/proj4-src.js
@@ -29,7 +29,10 @@ module.exports = function(proj4){
    proj4.Proj.projections.add(proj);
  });
 }
-},{"./lib/projections/aea":40,"./lib/projections/aeqd":41,"./lib/projections/cass":42,"./lib/projections/cea":43,"./lib/projections/eqc":44,"./lib/projections/eqdc":45,"./lib/projections/gnom":47,"./lib/projections/krovak":48,"./lib/projections/laea":49,"./lib/projections/lcc":50,"./lib/projections/mill":53,"./lib/projections/moll":54,"./lib/projections/nzmg":55,"./lib/projections/omerc":56,"./lib/projections/ortho":57,"./lib/projections/poly":58,"./lib/projections/sinu":59,"./lib/projec [...]
+},{"./lib/projections/aea":42,"./lib/projections/aeqd":43,"./lib/projections/cass":44,"./lib/projections/cea":45,"./lib/projections/eqc":46,"./lib/projections/eqdc":47,"./lib/projections/gnom":49,"./lib/projections/krovak":50,"./lib/projections/laea":51,"./lib/projections/lcc":52,"./lib/projections/mill":55,"./lib/projections/moll":56,"./lib/projections/nzmg":57,"./lib/projections/omerc":58,"./lib/projections/ortho":59,"./lib/projections/poly":60,"./lib/projections/sinu":61,"./lib/projec [...]
+module.exports = '2.3.16';
+
+},{}],1:[function(require,module,exports){
 var mgrs = require('mgrs');
 
 function Point(x, y, z) {
@@ -65,11 +68,14 @@ Point.prototype.toMGRS = function(accuracy) {
 };
 module.exports = Point;
 
-},{"mgrs":68}],2:[function(require,module,exports){
-var parseCode = require("./parseCode");
+},{"mgrs":70}],2:[function(require,module,exports){
+var parseCode = require('./parseCode');
 var extend = require('./extend');
 var projections = require('./projections');
 var deriveConstants = require('./deriveConstants');
+var Datum = require('./constants/Datum');
+var datum = require('./datum');
+
 
 function Projection(srsCode,callback) {
   if (!(this instanceof Projection)) {
@@ -85,27 +91,61 @@ function Projection(srsCode,callback) {
     callback(srsCode);
     return;
   }
-  var modifiedJSON = deriveConstants(json);
-  var ourProj = Projection.projections.get(modifiedJSON.projName);
-  if(ourProj){
-    extend(this, modifiedJSON);
-    extend(this, ourProj);
-    this.init();
-    callback(null, this);
-  }else{
+  var ourProj = Projection.projections.get(json.projName);
+  if(!ourProj){
     callback(srsCode);
+    return;
   }
+  if (json.datumCode && json.datumCode !== 'none') {
+    var datumDef = Datum[json.datumCode];
+    if (datumDef) {
+      json.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
+      json.ellps = datumDef.ellipse;
+      json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode;
+    }
+  }
+  json.k0 = json.k0 || 1.0;
+  json.axis = json.axis || 'enu';
+
+  var sphere = deriveConstants.sphere(json.a, json.b, json.rf, json.ellps, json.sphere);
+  var ecc = deriveConstants.eccentricity(sphere.a, sphere.b, sphere.rf, json.R_A);
+  var datumObj = json.datum || datum(json.datumCode, json.datum_params, sphere.a, sphere.b, ecc.es, ecc.ep2);
+
+  extend(this, json); // transfer everything over from the projection because we don't know what we'll need
+  extend(this, ourProj); // transfer all the methods from the projection
+
+  // copy the 4 things over we calulated in deriveConstants.sphere
+  this.a = sphere.a;
+  this.b = sphere.b;
+  this.rf = sphere.rf;
+  this.sphere = sphere.sphere;
+
+  // copy the 3 things we calculated in deriveConstants.eccentricity
+  this.es = ecc.es;
+  this.e = ecc.e;
+  this.ep2 = ecc.ep2;
+
+  // add in the datum object
+  this.datum = datumObj;
+
+  // init the projection
+  this.init();
+
+  // legecy callback from back in the day when it went to spatialreference.org
+  callback(null, this);
+
 }
 Projection.projections = projections;
 Projection.projections.start();
 module.exports = Projection;
 
-},{"./deriveConstants":33,"./extend":34,"./parseCode":37,"./projections":39}],3:[function(require,module,exports){
+},{"./constants/Datum":26,"./datum":31,"./deriveConstants":35,"./extend":36,"./parseCode":39,"./projections":41}],3:[function(require,module,exports){
 module.exports = function(crs, denorm, point) {
   var xin = point.x,
     yin = point.y,
     zin = point.z || 0.0;
   var v, t, i;
+  var out = {};
   for (i = 0; i < 3; i++) {
     if (denorm && i === 2 && point.z === undefined) {
       continue;
@@ -124,25 +164,25 @@ module.exports = function(crs, denorm, point) {
     }
     switch (crs.axis[i]) {
     case 'e':
-      point[t] = v;
+      out[t] = v;
       break;
     case 'w':
-      point[t] = -v;
+      out[t] = -v;
       break;
     case 'n':
-      point[t] = v;
+      out[t] = v;
       break;
     case 's':
-      point[t] = -v;
+      out[t] = -v;
       break;
     case 'u':
       if (point[t] !== undefined) {
-        point.z = v;
+        out.z = v;
       }
       break;
     case 'd':
       if (point[t] !== undefined) {
-        point.z = -v;
+        out.z = -v;
       }
       break;
     default:
@@ -150,7 +190,7 @@ module.exports = function(crs, denorm, point) {
       return null;
     }
   }
-  return point;
+  return out;
 };
 
 },{}],4:[function(require,module,exports){
@@ -160,7 +200,7 @@ var sign = require('./sign');
 module.exports = function(x) {
   return (Math.abs(x) < HALF_PI) ? x : (x - (sign(x) * Math.PI));
 };
-},{"./sign":21}],5:[function(require,module,exports){
+},{"./sign":22}],5:[function(require,module,exports){
 var TWO_PI = Math.PI * 2;
 // SPI is slightly greater than Math.PI, so values that exceed the -180..180
 // degree range by a tiny amount don't get wrapped. This prevents points that
@@ -172,35 +212,56 @@ var sign = require('./sign');
 module.exports = function(x) {
   return (Math.abs(x) <= SPI) ? x : (x - (sign(x) * TWO_PI));
 };
-},{"./sign":21}],6:[function(require,module,exports){
+},{"./sign":22}],6:[function(require,module,exports){
+var adjust_lon = require('./adjust_lon');
+
+module.exports = function(zone, lon) {
+  if (!zone) {
+    zone = Math.floor((adjust_lon(lon) + Math.PI) * 30 / Math.PI);
+
+    if (zone < 0) {
+      return 0;
+    }
+    else if (zone >= 60) {
+      return 59;
+    }
+  }
+  else {
+    if (zone > 0 && zone <= 60) {
+      return zone - 1;
+    }
+  }
+};
+
+},{"./adjust_lon":5}],7:[function(require,module,exports){
 module.exports = function(x) {
   if (Math.abs(x) > 1) {
     x = (x > 1) ? 1 : -1;
   }
   return Math.asin(x);
 };
-},{}],7:[function(require,module,exports){
+},{}],8:[function(require,module,exports){
 module.exports = function(x) {
   return (1 - 0.25 * x * (1 + x / 16 * (3 + 1.25 * x)));
 };
-},{}],8:[function(require,module,exports){
+},{}],9:[function(require,module,exports){
 module.exports = function(x) {
   return (0.375 * x * (1 + 0.25 * x * (1 + 0.46875 * x)));
 };
-},{}],9:[function(require,module,exports){
+},{}],10:[function(require,module,exports){
 module.exports = function(x) {
   return (0.05859375 * x * x * (1 + 0.75 * x));
 };
-},{}],10:[function(require,module,exports){
+},{}],11:[function(require,module,exports){
 module.exports = function(x) {
   return (x * x * x * (35 / 3072));
 };
-},{}],11:[function(require,module,exports){
+},{}],12:[function(require,module,exports){
 module.exports = function(a, e, sinphi) {
   var temp = e * sinphi;
   return a / Math.sqrt(1 - temp * temp);
 };
-},{}],12:[function(require,module,exports){
+},{}],13:[function(require,module,exports){
 module.exports = function(ml, e0, e1, e2, e3) {
   var phi;
   var dphi;
@@ -217,7 +278,7 @@ module.exports = function(ml, e0, e1, e2, e3) {
   //..reportError("IMLFN-CONV:Latitude failed to converge after 15 iterations");
   return NaN;
 };
-},{}],13:[function(require,module,exports){
+},{}],14:[function(require,module,exports){
 var HALF_PI = Math.PI/2;
 
 module.exports = function(eccent, q) {
@@ -250,16 +311,16 @@ module.exports = function(eccent, q) {
   //console.log("IQSFN-CONV:Latitude failed to converge after 30 iterations");
   return NaN;
 };
-},{}],14:[function(require,module,exports){
+},{}],15:[function(require,module,exports){
 module.exports = function(e0, e1, e2, e3, phi) {
   return (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi));
 };
-},{}],15:[function(require,module,exports){
+},{}],16:[function(require,module,exports){
 module.exports = function(eccent, sinphi, cosphi) {
   var con = eccent * sinphi;
   return cosphi / (Math.sqrt(1 - con * con));
 };
-},{}],16:[function(require,module,exports){
+},{}],17:[function(require,module,exports){
 var HALF_PI = Math.PI/2;
 module.exports = function(eccent, ts) {
   var eccnth = 0.5 * eccent;
@@ -276,7 +337,7 @@ module.exports = function(eccent, ts) {
   //console.log("phi2z has NoConvergence");
   return -9999;
 };
-},{}],17:[function(require,module,exports){
+},{}],18:[function(require,module,exports){
 var C00 = 1;
 var C02 = 0.25;
 var C04 = 0.046875;
@@ -301,7 +362,7 @@ module.exports = function(es) {
   en[4] = t * es * C88;
   return en;
 };
-},{}],18:[function(require,module,exports){
+},{}],19:[function(require,module,exports){
 var pj_mlfn = require("./pj_mlfn");
 var EPSLN = 1.0e-10;
 var MAX_ITER = 20;
@@ -322,13 +383,13 @@ module.exports = function(arg, es, en) {
   //..reportError("cass:pj_inv_mlfn: Convergence error");
   return phi;
 };
-},{"./pj_mlfn":19}],19:[function(require,module,exports){
+},{"./pj_mlfn":20}],20:[function(require,module,exports){
 module.exports = function(phi, sphi, cphi, en) {
   cphi *= sphi;
   sphi *= sphi;
   return (en[0] * phi - cphi * (en[1] + sphi * (en[2] + sphi * (en[3] + sphi * en[4]))));
 };
-},{}],20:[function(require,module,exports){
+},{}],21:[function(require,module,exports){
 module.exports = function(eccent, sinphi) {
   var con;
   if (eccent > 1.0e-7) {
@@ -339,15 +400,15 @@ module.exports = function(eccent, sinphi) {
     return (2 * sinphi);
   }
 };
-},{}],21:[function(require,module,exports){
+},{}],22:[function(require,module,exports){
 module.exports = function(x) {
   return x<0 ? -1 : 1;
 };
-},{}],22:[function(require,module,exports){
+},{}],23:[function(require,module,exports){
 module.exports = function(esinp, exp) {
   return (Math.pow((1 - esinp) / (1 + esinp), exp));
 };
-},{}],23:[function(require,module,exports){
+},{}],24:[function(require,module,exports){
 module.exports = function (array){
   var out = {
     x: array[0],
@@ -361,7 +422,7 @@ module.exports = function (array){
   }
   return out;
 };
-},{}],24:[function(require,module,exports){
+},{}],25:[function(require,module,exports){
 var HALF_PI = Math.PI/2;
 
 module.exports = function(eccent, phi, sinphi) {
@@ -370,7 +431,7 @@ module.exports = function(eccent, phi, sinphi) {
   con = Math.pow(((1 - con) / (1 + con)), com);
   return (Math.tan(0.5 * (HALF_PI - phi)) / con);
 };
-},{}],25:[function(require,module,exports){
+},{}],26:[function(require,module,exports){
 exports.wgs84 = {
   towgs84: "0,0,0",
   ellipse: "WGS84",
@@ -451,7 +512,7 @@ exports.rnb72 = {
   ellipse: "intl",
   datumName: "Reseau National Belge 1972"
 };
-},{}],26:[function(require,module,exports){
+},{}],27:[function(require,module,exports){
 exports.MERIT = {
   a: 6378137.0,
   rf: 298.257,
@@ -667,7 +728,7 @@ exports.sphere = {
   b: 6370997.0,
   ellipseName: "Normal Sphere (r=6370997)"
 };
-},{}],27:[function(require,module,exports){
+},{}],28:[function(require,module,exports){
 exports.greenwich = 0.0; //"0dE",
 exports.lisbon = -9.131906111111; //"9d07'54.862\"W",
 exports.paris = 2.337229166667; //"2d20'14.025\"E",
@@ -681,11 +742,11 @@ exports.brussels = 4.367975; //"4d22'4.71\"E",
 exports.stockholm = 18.058277777778; //"18d3'29.8\"E",
 exports.athens = 23.7163375; //"23d42'58.815\"E",
 exports.oslo = 10.722916666667; //"10d43'22.5\"E"
-},{}],28:[function(require,module,exports){
+},{}],29:[function(require,module,exports){
 exports.ft = {to_meter: 0.3048};
 exports['us-ft'] = {to_meter: 1200 / 3937};
 
-},{}],29:[function(require,module,exports){
+},{}],30:[function(require,module,exports){
 var proj = require('./Proj');
 var transform = require('./transform');
 var wgs84 = proj('WGS84');
@@ -750,426 +811,306 @@ function proj4(fromProj, toProj, coord) {
   }
 }
 module.exports = proj4;
-},{"./Proj":2,"./transform":66}],30:[function(require,module,exports){
-var HALF_PI = Math.PI/2;
+},{"./Proj":2,"./transform":68}],31:[function(require,module,exports){
 var PJD_3PARAM = 1;
 var PJD_7PARAM = 2;
-var PJD_GRIDSHIFT = 3;
 var PJD_WGS84 = 4; // WGS84 or equivalent
 var PJD_NODATUM = 5; // WGS84 or equivalent
 var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
-var AD_C = 1.0026000;
-var COS_67P5 = 0.38268343236508977;
-var datum = function(proj) {
-  if (!(this instanceof datum)) {
-    return new datum(proj);
-  }
-  this.datum_type = PJD_WGS84; //default setting
-  if (!proj) {
-    return;
-  }
-  if (proj.datumCode && proj.datumCode === 'none') {
-    this.datum_type = PJD_NODATUM;
-  }
 
-  if (proj.datum_params) {
-    this.datum_params = proj.datum_params.map(parseFloat);
-    if (this.datum_params[0] !== 0 || this.datum_params[1] !== 0 || this.datum_params[2] !== 0) {
-      this.datum_type = PJD_3PARAM;
-    }
-    if (this.datum_params.length > 3) {
-      if (this.datum_params[3] !== 0 || this.datum_params[4] !== 0 || this.datum_params[5] !== 0 || this.datum_params[6] !== 0) {
-        this.datum_type = PJD_7PARAM;
-        this.datum_params[3] *= SEC_TO_RAD;
-        this.datum_params[4] *= SEC_TO_RAD;
-        this.datum_params[5] *= SEC_TO_RAD;
-        this.datum_params[6] = (this.datum_params[6] / 1000000.0) + 1.0;
+function datum(datumCode, datum_params, a, b, es, ep2) {
+  var out = {};
+  out.datum_type = PJD_WGS84; //default setting
+  if (datumCode && datumCode === 'none') {
+    out.datum_type = PJD_NODATUM;
+  }
+
+  if (datum_params) {
+    out.datum_params = datum_params.map(parseFloat);
+    if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) {
+      out.datum_type = PJD_3PARAM;
+    }
+    if (out.datum_params.length > 3) {
+      if (out.datum_params[3] !== 0 || out.datum_params[4] !== 0 || out.datum_params[5] !== 0 || out.datum_params[6] !== 0) {
+        out.datum_type = PJD_7PARAM;
+        out.datum_params[3] *= SEC_TO_RAD;
+        out.datum_params[4] *= SEC_TO_RAD;
+        out.datum_params[5] *= SEC_TO_RAD;
+        out.datum_params[6] = (out.datum_params[6] / 1000000.0) + 1.0;
       }
     }
   }
 
-  // DGR 2011-03-21 : nadgrids support
-  this.datum_type = proj.grids ? PJD_GRIDSHIFT : this.datum_type;
 
-  this.a = proj.a; //datum object also uses these values
-  this.b = proj.b;
-  this.es = proj.es;
-  this.ep2 = proj.ep2;
-  if (this.datum_type === PJD_GRIDSHIFT) {
-    this.grids = proj.grids;
+  out.a = a; //datum object also uses these values
+  out.b = b;
+  out.es = es;
+  out.ep2 = ep2;
+  return out;
+}
+
+module.exports = datum;
+
+},{}],32:[function(require,module,exports){
+'use strict';
+var PJD_3PARAM = 1;
+var PJD_7PARAM = 2;
+var HALF_PI = Math.PI/2;
+
+exports.compareDatums = function(source, dest) {
+  if (source.datum_type !== dest.datum_type) {
+    return false; // false, datums are not equal
+  } else if (source.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
+    // the tolerence for es is to ensure that GRS80 and WGS84
+    // are considered identical
+    return false;
+  } else if (source.datum_type === PJD_3PARAM) {
+    return (this.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2]);
+  } else if (source.datum_type === PJD_7PARAM) {
+    return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2] && source.datum_params[3] === dest.datum_params[3] && source.datum_params[4] === dest.datum_params[4] && source.datum_params[5] === dest.datum_params[5] && source.datum_params[6] === dest.datum_params[6]);
+  } else {
+    return true; // datums are equal
   }
-};
-datum.prototype = {
+}; // cs_compare_datums()
 
+/*
+ * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
+ * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
+ * according to the current ellipsoid parameters.
+ *
+ *    Latitude  : Geodetic latitude in radians                     (input)
+ *    Longitude : Geodetic longitude in radians                    (input)
+ *    Height    : Geodetic height, in meters                       (input)
+ *    X         : Calculated Geocentric X coordinate, in meters    (output)
+ *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
+ *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
+ *
+ */
+exports.geodeticToGeocentric = function(p, es, a) {
+  var Longitude = p.x;
+  var Latitude = p.y;
+  var Height = p.z ? p.z : 0; //Z value not always supplied
 
-  /****************************************************************/
-  // cs_compare_datums()
-  //   Returns TRUE if the two datums match, otherwise FALSE.
-  compare_datums: function(dest) {
-    if (this.datum_type !== dest.datum_type) {
-      return false; // false, datums are not equal
-    }
-    else if (this.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
-      // the tolerence for es is to ensure that GRS80 and WGS84
-      // are considered identical
-      return false;
-    }
-    else if (this.datum_type === PJD_3PARAM) {
-      return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2]);
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2] && this.datum_params[3] === dest.datum_params[3] && this.datum_params[4] === dest.datum_params[4] && this.datum_params[5] === dest.datum_params[5] && this.datum_params[6] === dest.datum_params[6]);
-    }
-    else if (this.datum_type === PJD_GRIDSHIFT || dest.datum_type === PJD_GRIDSHIFT) {
-      //alert("ERROR: Grid shift transformations are not implemented.");
-      //return false
-      //DGR 2012-07-29 lazy ...
-      return this.nadgrids === dest.nadgrids;
-    }
-    else {
-      return true; // datums are equal
-    }
-  }, // cs_compare_datums()
+  var Rn; /*  Earth radius at location  */
+  var Sin_Lat; /*  Math.sin(Latitude)  */
+  var Sin2_Lat; /*  Square of Math.sin(Latitude)  */
+  var Cos_Lat; /*  Math.cos(Latitude)  */
 
   /*
-   * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
-   * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
-   * according to the current ellipsoid parameters.
-   *
-   *    Latitude  : Geodetic latitude in radians                     (input)
-   *    Longitude : Geodetic longitude in radians                    (input)
-   *    Height    : Geodetic height, in meters                       (input)
-   *    X         : Calculated Geocentric X coordinate, in meters    (output)
-   *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
-   *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
-   *
+   ** Don't blow up if Latitude is just a little out of the value
+   ** range as it may just be a rounding issue.  Also removed longitude
+   ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
    */
-  geodetic_to_geocentric: function(p) {
-    var Longitude = p.x;
-    var Latitude = p.y;
-    var Height = p.z ? p.z : 0; //Z value not always supplied
-    var X; // output
-    var Y;
-    var Z;
-
-    var Error_Code = 0; //  GEOCENT_NO_ERROR;
-    var Rn; /*  Earth radius at location  */
-    var Sin_Lat; /*  Math.sin(Latitude)  */
-    var Sin2_Lat; /*  Square of Math.sin(Latitude)  */
-    var Cos_Lat; /*  Math.cos(Latitude)  */
-
-    /*
-     ** Don't blow up if Latitude is just a little out of the value
-     ** range as it may just be a rounding issue.  Also removed longitude
-     ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
-     */
-    if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
-      Latitude = -HALF_PI;
-    }
-    else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
-      Latitude = HALF_PI;
-    }
-    else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
-      /* Latitude out of range */
-      //..reportError('geocent:lat out of range:' + Latitude);
-      return null;
-    }
+  if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
+    Latitude = -HALF_PI;
+  } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
+    Latitude = HALF_PI;
+  } else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
+    /* Latitude out of range */
+    //..reportError('geocent:lat out of range:' + Latitude);
+    return null;
+  }
 
-    if (Longitude > Math.PI) {
-      Longitude -= (2 * Math.PI);
-    }
-    Sin_Lat = Math.sin(Latitude);
-    Cos_Lat = Math.cos(Latitude);
-    Sin2_Lat = Sin_Lat * Sin_Lat;
-    Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
-    X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
-    Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
-    Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
-
-    p.x = X;
-    p.y = Y;
-    p.z = Z;
-    return Error_Code;
-  }, // cs_geodetic_to_geocentric()
-
-
-  geocentric_to_geodetic: function(p) {
-    /* local defintions and variables */
-    /* end-criterium of loop, accuracy of sin(Latitude) */
-    var genau = 1e-12;
-    var genau2 = (genau * genau);
-    var maxiter = 30;
-
-    var P; /* distance between semi-minor axis and location */
-    var RR; /* distance between center and location */
-    var CT; /* sin of geocentric latitude */
-    var ST; /* cos of geocentric latitude */
-    var RX;
-    var RK;
-    var RN; /* Earth radius at location */
-    var CPHI0; /* cos of start or old geodetic latitude in iterations */
-    var SPHI0; /* sin of start or old geodetic latitude in iterations */
-    var CPHI; /* cos of searched geodetic latitude */
-    var SPHI; /* sin of searched geodetic latitude */
-    var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
-    var At_Pole; /* indicates location is in polar region */
-    var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
-
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0.0; //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    At_Pole = false;
-    P = Math.sqrt(X * X + Y * Y);
-    RR = Math.sqrt(X * X + Y * Y + Z * Z);
-
-    /*      special cases for latitude and longitude */
-    if (P / this.a < genau) {
-
-      /*  special case, if P=0. (X=0., Y=0.) */
-      At_Pole = true;
-      Longitude = 0.0;
-
-      /*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
-       *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
-      if (RR / this.a < genau) {
-        Latitude = HALF_PI;
-        Height = -this.b;
-        return;
-      }
+  if (Longitude > Math.PI) {
+    Longitude -= (2 * Math.PI);
+  }
+  Sin_Lat = Math.sin(Latitude);
+  Cos_Lat = Math.cos(Latitude);
+  Sin2_Lat = Sin_Lat * Sin_Lat;
+  Rn = a / (Math.sqrt(1.0e0 - es * Sin2_Lat));
+  return {
+    x: (Rn + Height) * Cos_Lat * Math.cos(Longitude),
+    y: (Rn + Height) * Cos_Lat * Math.sin(Longitude),
+    z: ((Rn * (1 - es)) + Height) * Sin_Lat
+  };
+}; // cs_geodetic_to_geocentric()
+
+
+exports.geocentricToGeodetic = function(p, es, a, b) {
+  /* local defintions and variables */
+  /* end-criterium of loop, accuracy of sin(Latitude) */
+  var genau = 1e-12;
+  var genau2 = (genau * genau);
+  var maxiter = 30;
+
+  var P; /* distance between semi-minor axis and location */
+  var RR; /* distance between center and location */
+  var CT; /* sin of geocentric latitude */
+  var ST; /* cos of geocentric latitude */
+  var RX;
+  var RK;
+  var RN; /* Earth radius at location */
+  var CPHI0; /* cos of start or old geodetic latitude in iterations */
+  var SPHI0; /* sin of start or old geodetic latitude in iterations */
+  var CPHI; /* cos of searched geodetic latitude */
+  var SPHI; /* sin of searched geodetic latitude */
+  var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
+  var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
+
+  var X = p.x;
+  var Y = p.y;
+  var Z = p.z ? p.z : 0.0; //Z value not always supplied
+  var Longitude;
+  var Latitude;
+  var Height;
+
+  P = Math.sqrt(X * X + Y * Y);
+  RR = Math.sqrt(X * X + Y * Y + Z * Z);
+
+  /*      special cases for latitude and longitude */
+  if (P / a < genau) {
+
+    /*  special case, if P=0. (X=0., Y=0.) */
+    Longitude = 0.0;
+
+    /*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
+     *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
+    if (RR / a < genau) {
+      Latitude = HALF_PI;
+      Height = -b;
+      return {
+        x: p.x,
+        y: p.y,
+        z: p.z
+      };
     }
-    else {
-      /*  ellipsoidal (geodetic) longitude
-       *  interval: -PI < Longitude <= +PI */
-      Longitude = Math.atan2(Y, X);
-    }
-
-    /* --------------------------------------------------------------
-     * Following iterative algorithm was developped by
-     * "Institut for Erdmessung", University of Hannover, July 1988.
-     * Internet: www.ife.uni-hannover.de
-     * Iterative computation of CPHI,SPHI and Height.
-     * Iteration of CPHI and SPHI to 10**-12 radian resp.
-     * 2*10**-7 arcsec.
-     * --------------------------------------------------------------
-     */
-    CT = Z / RR;
-    ST = P / RR;
-    RX = 1.0 / Math.sqrt(1.0 - this.es * (2.0 - this.es) * ST * ST);
-    CPHI0 = ST * (1.0 - this.es) * RX;
-    SPHI0 = CT * RX;
-    iter = 0;
-
-    /* loop to find sin(Latitude) resp. Latitude
-     * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
-    do {
-      iter++;
-      RN = this.a / Math.sqrt(1.0 - this.es * SPHI0 * SPHI0);
-
-      /*  ellipsoidal (geodetic) height */
-      Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - this.es * SPHI0 * SPHI0);
-
-      RK = this.es * RN / (RN + Height);
-      RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
-      CPHI = ST * (1.0 - RK) * RX;
-      SPHI = CT * RX;
-      SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
-      CPHI0 = CPHI;
-      SPHI0 = SPHI;
-    }
-    while (SDPHI * SDPHI > genau2 && iter < maxiter);
-
-    /*      ellipsoidal (geodetic) latitude */
-    Latitude = Math.atan(SPHI / Math.abs(CPHI));
-
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // cs_geocentric_to_geodetic()
-
-  /** Convert_Geocentric_To_Geodetic
-   * The method used here is derived from 'An Improved Algorithm for
-   * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
+  } else {
+    /*  ellipsoidal (geodetic) longitude
+     *  interval: -PI < Longitude <= +PI */
+    Longitude = Math.atan2(Y, X);
+  }
+
+  /* --------------------------------------------------------------
+   * Following iterative algorithm was developped by
+   * "Institut for Erdmessung", University of Hannover, July 1988.
+   * Internet: www.ife.uni-hannover.de
+   * Iterative computation of CPHI,SPHI and Height.
+   * Iteration of CPHI and SPHI to 10**-12 radian resp.
+   * 2*10**-7 arcsec.
+   * --------------------------------------------------------------
    */
-  geocentric_to_geodetic_noniter: function(p) {
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0; //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    var W; /* distance from Z axis */
-    var W2; /* square of distance from Z axis */
-    var T0; /* initial estimate of vertical component */
-    var T1; /* corrected estimate of vertical component */
-    var S0; /* initial estimate of horizontal component */
-    var S1; /* corrected estimate of horizontal component */
-    var Sin_B0; /* Math.sin(B0), B0 is estimate of Bowring aux variable */
-    var Sin3_B0; /* cube of Math.sin(B0) */
-    var Cos_B0; /* Math.cos(B0) */
-    var Sin_p1; /* Math.sin(phi1), phi1 is estimated latitude */
-    var Cos_p1; /* Math.cos(phi1) */
-    var Rn; /* Earth radius at location */
-    var Sum; /* numerator of Math.cos(phi1) */
-    var At_Pole; /* indicates location is in polar region */
-
-    X = parseFloat(X); // cast from string to float
-    Y = parseFloat(Y);
-    Z = parseFloat(Z);
-
-    At_Pole = false;
-    if (X !== 0.0) {
-      Longitude = Math.atan2(Y, X);
-    }
-    else {
-      if (Y > 0) {
-        Longitude = HALF_PI;
-      }
-      else if (Y < 0) {
-        Longitude = -HALF_PI;
-      }
-      else {
-        At_Pole = true;
-        Longitude = 0.0;
-        if (Z > 0.0) { /* north pole */
-          Latitude = HALF_PI;
-        }
-        else if (Z < 0.0) { /* south pole */
-          Latitude = -HALF_PI;
-        }
-        else { /* center of earth */
-          Latitude = HALF_PI;
-          Height = -this.b;
-          return;
-        }
-      }
-    }
-    W2 = X * X + Y * Y;
-    W = Math.sqrt(W2);
-    T0 = Z * AD_C;
-    S0 = Math.sqrt(T0 * T0 + W2);
-    Sin_B0 = T0 / S0;
-    Cos_B0 = W / S0;
-    Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
-    T1 = Z + this.b * this.ep2 * Sin3_B0;
-    Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
-    S1 = Math.sqrt(T1 * T1 + Sum * Sum);
-    Sin_p1 = T1 / S1;
-    Cos_p1 = Sum / S1;
-    Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
-    if (Cos_p1 >= COS_67P5) {
-      Height = W / Cos_p1 - Rn;
-    }
-    else if (Cos_p1 <= -COS_67P5) {
-      Height = W / -Cos_p1 - Rn;
-    }
-    else {
-      Height = Z / Sin_p1 + Rn * (this.es - 1.0);
-    }
-    if (At_Pole === false) {
-      Latitude = Math.atan(Sin_p1 / Cos_p1);
-    }
+  CT = Z / RR;
+  ST = P / RR;
+  RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST);
+  CPHI0 = ST * (1.0 - es) * RX;
+  SPHI0 = CT * RX;
+  iter = 0;
+
+  /* loop to find sin(Latitude) resp. Latitude
+   * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
+  do {
+    iter++;
+    RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0);
 
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // geocentric_to_geodetic_noniter()
-
-  /****************************************************************/
-  // pj_geocentic_to_wgs84( p )
-  //  p = point to transform in geocentric coordinates (x,y,z)
-  geocentric_to_wgs84: function(p) {
-
-    if (this.datum_type === PJD_3PARAM) {
-      // if( x[io] === HUGE_VAL )
-      //    continue;
-      p.x += this.datum_params[0];
-      p.y += this.datum_params[1];
-      p.z += this.datum_params[2];
-
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      var Dx_BF = this.datum_params[0];
-      var Dy_BF = this.datum_params[1];
-      var Dz_BF = this.datum_params[2];
-      var Rx_BF = this.datum_params[3];
-      var Ry_BF = this.datum_params[4];
-      var Rz_BF = this.datum_params[5];
-      var M_BF = this.datum_params[6];
-      // if( x[io] === HUGE_VAL )
-      //    continue;
-      var x_out = M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF;
-      var y_out = M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF;
-      var z_out = M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF;
-      p.x = x_out;
-      p.y = y_out;
-      p.z = z_out;
-    }
-  }, // cs_geocentric_to_wgs84
-
-  /****************************************************************/
-  // pj_geocentic_from_wgs84()
-  //  coordinate system definition,
-  //  point to transform in geocentric coordinates (x,y,z)
-  geocentric_from_wgs84: function(p) {
-
-    if (this.datum_type === PJD_3PARAM) {
-      //if( x[io] === HUGE_VAL )
-      //    continue;
-      p.x -= this.datum_params[0];
-      p.y -= this.datum_params[1];
-      p.z -= this.datum_params[2];
-
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      var Dx_BF = this.datum_params[0];
-      var Dy_BF = this.datum_params[1];
-      var Dz_BF = this.datum_params[2];
-      var Rx_BF = this.datum_params[3];
-      var Ry_BF = this.datum_params[4];
-      var Rz_BF = this.datum_params[5];
-      var M_BF = this.datum_params[6];
-      var x_tmp = (p.x - Dx_BF) / M_BF;
-      var y_tmp = (p.y - Dy_BF) / M_BF;
-      var z_tmp = (p.z - Dz_BF) / M_BF;
-      //if( x[io] === HUGE_VAL )
-      //    continue;
-
-      p.x = x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp;
-      p.y = -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp;
-      p.z = Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp;
-    } //cs_geocentric_from_wgs84()
+    /*  ellipsoidal (geodetic) height */
+    Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0);
+
+    RK = es * RN / (RN + Height);
+    RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
+    CPHI = ST * (1.0 - RK) * RX;
+    SPHI = CT * RX;
+    SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
+    CPHI0 = CPHI;
+    SPHI0 = SPHI;
   }
-};
+  while (SDPHI * SDPHI > genau2 && iter < maxiter);
+
+  /*      ellipsoidal (geodetic) latitude */
+  Latitude = Math.atan(SPHI / Math.abs(CPHI));
+  return {
+    x: Longitude,
+    y: Latitude,
+    z: Height
+  };
+}; // cs_geocentric_to_geodetic()
+
+
+/****************************************************************/
+// pj_geocentic_to_wgs84( p )
+//  p = point to transform in geocentric coordinates (x,y,z)
+
 
 /** point object, nothing fancy, just allows values to be
     passed back and forth by reference rather than by value.
     Other point classes may be used as long as they have
     x and y properties, which will get modified in the transform method.
 */
-module.exports = datum;
+exports.geocentricToWgs84 = function(p, datum_type, datum_params) {
+
+  if (datum_type === PJD_3PARAM) {
+    // if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: p.x + datum_params[0],
+      y: p.y + datum_params[1],
+      z: p.z + datum_params[2],
+    };
+  } else if (datum_type === PJD_7PARAM) {
+    var Dx_BF = datum_params[0];
+    var Dy_BF = datum_params[1];
+    var Dz_BF = datum_params[2];
+    var Rx_BF = datum_params[3];
+    var Ry_BF = datum_params[4];
+    var Rz_BF = datum_params[5];
+    var M_BF = datum_params[6];
+    // if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF,
+      y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF,
+      z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF
+    };
+  }
+}; // cs_geocentric_to_wgs84
+
+/****************************************************************/
+// pj_geocentic_from_wgs84()
+//  coordinate system definition,
+//  point to transform in geocentric coordinates (x,y,z)
+exports.geocentricFromWgs84 = function(p, datum_type, datum_params) {
+
+  if (datum_type === PJD_3PARAM) {
+    //if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: p.x - datum_params[0],
+      y: p.y - datum_params[1],
+      z: p.z - datum_params[2],
+    };
+
+  } else if (datum_type === PJD_7PARAM) {
+    var Dx_BF = datum_params[0];
+    var Dy_BF = datum_params[1];
+    var Dz_BF = datum_params[2];
+    var Rx_BF = datum_params[3];
+    var Ry_BF = datum_params[4];
+    var Rz_BF = datum_params[5];
+    var M_BF = datum_params[6];
+    var x_tmp = (p.x - Dx_BF) / M_BF;
+    var y_tmp = (p.y - Dy_BF) / M_BF;
+    var z_tmp = (p.z - Dz_BF) / M_BF;
+    //if( x[io] === HUGE_VAL )
+    //    continue;
+
+    return {
+      x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp,
+      y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp,
+      z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp
+    };
+  } //cs_geocentric_from_wgs84()
+};
 
-},{}],31:[function(require,module,exports){
+},{}],33:[function(require,module,exports){
 var PJD_3PARAM = 1;
 var PJD_7PARAM = 2;
-var PJD_GRIDSHIFT = 3;
 var PJD_NODATUM = 5; // WGS84 or equivalent
-var SRS_WGS84_SEMIMAJOR = 6378137; // only used in grid shift transforms
-var SRS_WGS84_ESQUARED = 0.006694379990141316; //DGR: 2012-07-29
+var datum = require('./datumUtils');
+function checkParams(type) {
+  return (type === PJD_3PARAM || type === PJD_7PARAM);
+}
 module.exports = function(source, dest, point) {
-  var wp, i, l;
-
-  function checkParams(fallback) {
-    return (fallback === PJD_3PARAM || fallback === PJD_7PARAM);
-  }
   // Short cut if the datums are identical.
-  if (source.compare_datums(dest)) {
+  if (datum.compareDatums(source, dest)) {
     return point; // in this case, zero is sucess,
     // whereas cs_compare_datums returns 1 to indicate TRUE
     // confusing, should fix this
@@ -1180,83 +1121,27 @@ module.exports = function(source, dest, point) {
     return point;
   }
 
-  //DGR: 2012-07-29 : add nadgrids support (begin)
-  var src_a = source.a;
-  var src_es = source.es;
+  // If this datum requires grid shifts, then apply it to geodetic coordinates.
 
-  var dst_a = dest.a;
-  var dst_es = dest.es;
+  // Do we need to go through geocentric coordinates?
+  if (source.es === dest.es && source.a === dest.a && !checkParams(source.datum_type) &&  !checkParams(dest.datum_type)) {
+    return point;
+  }
 
-  var fallback = source.datum_type;
-  // If this datum requires grid shifts, then apply it to geodetic coordinates.
-  if (fallback === PJD_GRIDSHIFT) {
-    if (this.apply_gridshift(source, 0, point) === 0) {
-      source.a = SRS_WGS84_SEMIMAJOR;
-      source.es = SRS_WGS84_ESQUARED;
-    }
-    else {
-      // try 3 or 7 params transformation or nothing ?
-      if (!source.datum_params) {
-        source.a = src_a;
-        source.es = source.es;
-        return point;
-      }
-      wp = 1;
-      for (i = 0, l = source.datum_params.length; i < l; i++) {
-        wp *= source.datum_params[i];
-      }
-      if (wp === 0) {
-        source.a = src_a;
-        source.es = source.es;
-        return point;
-      }
-      if (source.datum_params.length > 3) {
-        fallback = PJD_7PARAM;
-      }
-      else {
-        fallback = PJD_3PARAM;
-      }
-    }
+  // Convert to geocentric coordinates.
+  point = datum.geodeticToGeocentric(point, source.es, source.a);
+  // Convert between datums
+  if (checkParams(source.datum_type)) {
+    point = datum.geocentricToWgs84(point, source.datum_type, source.datum_params);
   }
-  if (dest.datum_type === PJD_GRIDSHIFT) {
-    dest.a = SRS_WGS84_SEMIMAJOR;
-    dest.es = SRS_WGS84_ESQUARED;
+  if (checkParams(dest.datum_type)) {
+    point = datum.geocentricFromWgs84(point, dest.datum_type, dest.datum_params);
   }
-  // Do we need to go through geocentric coordinates?
-  if (source.es !== dest.es || source.a !== dest.a || checkParams(fallback) || checkParams(dest.datum_type)) {
-    //DGR: 2012-07-29 : add nadgrids support (end)
-    // Convert to geocentric coordinates.
-    source.geodetic_to_geocentric(point);
-    // CHECK_RETURN;
-    // Convert between datums
-    if (checkParams(source.datum_type)) {
-      source.geocentric_to_wgs84(point);
-      // CHECK_RETURN;
-    }
-    if (checkParams(dest.datum_type)) {
-      dest.geocentric_from_wgs84(point);
-      // CHECK_RETURN;
-    }
-    // Convert back to geodetic coordinates
-    dest.geocentric_to_geodetic(point);
-    // CHECK_RETURN;
-  }
-  // Apply grid shift to destination if required
-  if (dest.datum_type === PJD_GRIDSHIFT) {
-    this.apply_gridshift(dest, 1, point);
-    // CHECK_RETURN;
-  }
-
-  source.a = src_a;
-  source.es = src_es;
-  dest.a = dst_a;
-  dest.es = dst_es;
+  return datum.geocentricToGeodetic(point, dest.es, dest.a, dest.b);
 
-  return point;
 };
 
-
-},{}],32:[function(require,module,exports){
+},{"./datumUtils":32}],34:[function(require,module,exports){
 var globals = require('./global');
 var parseProj = require('./projString');
 var wkt = require('./wkt');
@@ -1313,65 +1198,62 @@ function defs(name) {
 globals(defs);
 module.exports = defs;
 
-},{"./global":35,"./projString":38,"./wkt":67}],33:[function(require,module,exports){
-var Datum = require('./constants/Datum');
-var Ellipsoid = require('./constants/Ellipsoid');
-var extend = require('./extend');
-var datum = require('./datum');
-var EPSLN = 1.0e-10;
+},{"./global":37,"./projString":40,"./wkt":69}],35:[function(require,module,exports){
 // ellipoid pj_set_ell.c
 var SIXTH = 0.1666666666666666667;
 /* 1/6 */
 var RA4 = 0.04722222222222222222;
 /* 17/360 */
 var RA6 = 0.02215608465608465608;
-module.exports = function(json) {
-  // DGR 2011-03-20 : nagrids -> nadgrids
-  if (json.datumCode && json.datumCode !== 'none') {
-    var datumDef = Datum[json.datumCode];
-    if (datumDef) {
-      json.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
-      json.ellps = datumDef.ellipse;
-      json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode;
-    }
-  }
-  if (!json.a) { // do we have an ellipsoid?
-    var ellipse = Ellipsoid[json.ellps] ? Ellipsoid[json.ellps] : Ellipsoid.WGS84;
-    extend(json, ellipse);
-  }
-  if (json.rf && !json.b) {
-    json.b = (1.0 - 1.0 / json.rf) * json.a;
-  }
-  if (json.rf === 0 || Math.abs(json.a - json.b) < EPSLN) {
-    json.sphere = true;
-    json.b = json.a;
-  }
-  json.a2 = json.a * json.a; // used in geocentric
-  json.b2 = json.b * json.b; // used in geocentric
-  json.es = (json.a2 - json.b2) / json.a2; // e ^ 2
-  json.e = Math.sqrt(json.es); // eccentricity
-  if (json.R_A) {
-    json.a *= 1 - json.es * (SIXTH + json.es * (RA4 + json.es * RA6));
-    json.a2 = json.a * json.a;
-    json.b2 = json.b * json.b;
-    json.es = 0;
-  }
-  json.ep2 = (json.a2 - json.b2) / json.b2; // used in geocentric
-  if (!json.k0) {
-    json.k0 = 1.0; //default value
+var EPSLN = 1.0e-10;
+var Ellipsoid = require('./constants/Ellipsoid');
+
+exports.eccentricity = function(a, b, rf, R_A) {
+  var a2 = a * a; // used in geocentric
+  var b2 = b * b; // used in geocentric
+  var es = (a2 - b2) / a2; // e ^ 2
+  var e = 0;
+  if (R_A) {
+    a *= 1 - es * (SIXTH + es * (RA4 + es * RA6));
+    a2 = a * a;
+    es = 0;
+  } else {
+    e = Math.sqrt(es); // eccentricity
   }
-  //DGR 2010-11-12: axis
-  if (!json.axis) {
-    json.axis = "enu";
+  var ep2 = (a2 - b2) / b2; // used in geocentric
+  return {
+    es: es,
+    e: e,
+    ep2: ep2
+  };
+};
+exports.sphere = function (a, b, rf, ellps, sphere) {
+  if (!a) { // do we have an ellipsoid?
+    var ellipse = Ellipsoid[ellps];
+    if (!ellipse) {
+      ellipse = Ellipsoid.WGS84;
+    }
+    a = ellipse.a;
+    b = ellipse.b;
+    rf = ellipse.rf;
   }
 
-  if (!json.datum) {
-    json.datum = datum(json);
+  if (rf && !b) {
+    b = (1.0 - 1.0 / rf) * a;
+  }
+  if (rf === 0 || Math.abs(a - b) < EPSLN) {
+    sphere = true;
+    b = a;
   }
-  return json;
+  return {
+    a: a,
+    b: b,
+    rf: rf,
+    sphere: sphere
+  };
 };
 
-},{"./constants/Datum":25,"./constants/Ellipsoid":26,"./datum":30,"./extend":34}],34:[function(require,module,exports){
+},{"./constants/Ellipsoid":27}],36:[function(require,module,exports){
 module.exports = function(destination, source) {
   destination = destination || {};
   var value, property;
@@ -1387,7 +1269,7 @@ module.exports = function(destination, source) {
   return destination;
 };
 
-},{}],35:[function(require,module,exports){
+},{}],37:[function(require,module,exports){
 module.exports = function(defs) {
   defs('EPSG:4326', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees");
   defs('EPSG:4269', "+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees");
@@ -1400,20 +1282,21 @@ module.exports = function(defs) {
   defs['EPSG:102113'] = defs['EPSG:3857'];
 };
 
-},{}],36:[function(require,module,exports){
+},{}],38:[function(require,module,exports){
 var proj4 = require('./core');
 proj4.defaultDatum = 'WGS84'; //default datum
 proj4.Proj = require('./Proj');
 proj4.WGS84 = new proj4.Proj('WGS84');
 proj4.Point = require('./Point');
-proj4.toPoint = require("./common/toPoint");
+proj4.toPoint = require('./common/toPoint');
 proj4.defs = require('./defs');
 proj4.transform = require('./transform');
 proj4.mgrs = require('mgrs');
-proj4.version = require('../package.json').version;
+proj4.version = require('./version');
 require('./includedProjections')(proj4);
 module.exports = proj4;
-},{"../package.json":69,"./Point":1,"./Proj":2,"./common/toPoint":23,"./core":29,"./defs":32,"./includedProjections":"./includedProjections","./transform":66,"mgrs":68}],37:[function(require,module,exports){
+
+},{"./Point":1,"./Proj":2,"./common/toPoint":24,"./core":30,"./defs":34,"./includedProjections":"./includedProjections","./transform":68,"./version":"./lib/version","mgrs":70}],39:[function(require,module,exports){
 var defs = require('./defs');
 var wkt = require('./wkt');
 var projStr = require('./projString');
@@ -1423,11 +1306,12 @@ function testObj(code){
 function testDef(code){
   return code in defs;
 }
+var codeWords = ['GEOGCS','GEOCCS','PROJCS','LOCAL_CS'];
+
 function testWKT(code){
-  var codeWords = ['GEOGCS','GEOCCS','PROJCS','LOCAL_CS'];
-  return codeWords.reduce(function(a,b){
-    return a+1+code.indexOf(b);
-  },0);
+  return codeWords.some(function (word) {
+    return code.indexOf(word) > -1;
+  });
 }
 function testProj(code){
   return code[0] === '+';
@@ -1438,10 +1322,10 @@ function parse(code){
     if (testDef(code)) {
       return defs[code];
     }
-    else if (testWKT(code)) {
+    if (testWKT(code)) {
       return wkt(code);
     }
-    else if (testProj(code)) {
+    if (testProj(code)) {
       return projStr(code);
     }
   }else{
@@ -1450,23 +1334,24 @@ function parse(code){
 }
 
 module.exports = parse;
-},{"./defs":32,"./projString":38,"./wkt":67}],38:[function(require,module,exports){
+
+},{"./defs":34,"./projString":40,"./wkt":69}],40:[function(require,module,exports){
 var D2R = 0.01745329251994329577;
 var PrimeMeridian = require('./constants/PrimeMeridian');
 var units = require('./constants/units');
 
 module.exports = function(defData) {
   var self = {};
-  var paramObj = {};
-  defData.split("+").map(function(v) {
+  var paramObj = defData.split('+').map(function(v) {
     return v.trim();
   }).filter(function(a) {
     return a;
-  }).forEach(function(a) {
-    var split = a.split("=");
+  }).reduce(function(p, a) {
+    var split = a.split('=');
     split.push(true);
-    paramObj[split[0].toLowerCase()] = split[1];
-  });
+    p[split[0].toLowerCase()] = split[1];
+    return p;
+  }, {});
   var paramName, paramVal, paramOutname;
   var params = {
     proj: 'projName',
@@ -1584,7 +1469,7 @@ module.exports = function(defData) {
   return self;
 };
 
-},{"./constants/PrimeMeridian":27,"./constants/units":28}],39:[function(require,module,exports){
+},{"./constants/PrimeMeridian":28,"./constants/units":29}],41:[function(require,module,exports){
 var projs = [
   require('./projections/merc'),
   require('./projections/longlat')
@@ -1620,7 +1505,7 @@ exports.start = function() {
   projs.forEach(add);
 };
 
-},{"./projections/longlat":51,"./projections/merc":52}],40:[function(require,module,exports){
+},{"./projections/longlat":53,"./projections/merc":54}],42:[function(require,module,exports){
 var EPSLN = 1.0e-10;
 var msfnz = require('../common/msfnz');
 var qsfnz = require('../common/qsfnz');
@@ -1743,7 +1628,7 @@ exports.phi1z = function(eccent, qs) {
 };
 exports.names = ["Albers_Conic_Equal_Area", "Albers", "aea"];
 
-},{"../common/adjust_lon":5,"../common/asinz":6,"../common/msfnz":15,"../common/qsfnz":20}],41:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/asinz":7,"../common/msfnz":16,"../common/qsfnz":21}],43:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var HALF_PI = Math.PI/2;
 var EPSLN = 1.0e-10;
@@ -1942,7 +1827,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Azimuthal_Equidistant", "aeqd"];
 
-},{"../common/adjust_lon":5,"../common/asinz":6,"../common/e0fn":7,"../common/e1fn":8,"../common/e2fn":9,"../common/e3fn":10,"../common/gN":11,"../common/imlfn":12,"../common/mlfn":14}],42:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/asinz":7,"../common/e0fn":8,"../common/e1fn":9,"../common/e2fn":10,"../common/e3fn":11,"../common/gN":12,"../common/imlfn":13,"../common/mlfn":15}],44:[function(require,module,exports){
 var mlfn = require('../common/mlfn');
 var e0fn = require('../common/e0fn');
 var e1fn = require('../common/e1fn');
@@ -2046,7 +1931,7 @@ exports.inverse = function(p) {
 
 };
 exports.names = ["Cassini", "Cassini_Soldner", "cass"];
-},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":7,"../common/e1fn":8,"../common/e2fn":9,"../common/e3fn":10,"../common/gN":11,"../common/imlfn":12,"../common/mlfn":14}],43:[function(require,module,exports){
+},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":8,"../common/e1fn":9,"../common/e2fn":10,"../common/e3fn":11,"../common/gN":12,"../common/imlfn":13,"../common/mlfn":15}],45:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var qsfnz = require('../common/qsfnz');
 var msfnz = require('../common/msfnz');
@@ -2111,7 +1996,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["cea"];
 
-},{"../common/adjust_lon":5,"../common/iqsfnz":13,"../common/msfnz":15,"../common/qsfnz":20}],44:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/iqsfnz":14,"../common/msfnz":16,"../common/qsfnz":21}],46:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var adjust_lat = require('../common/adjust_lat');
 exports.init = function() {
@@ -2154,7 +2039,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Equirectangular", "Equidistant_Cylindrical", "eqc"];
 
-},{"../common/adjust_lat":4,"../common/adjust_lon":5}],45:[function(require,module,exports){
+},{"../common/adjust_lat":4,"../common/adjust_lon":5}],47:[function(require,module,exports){
 var e0fn = require('../common/e0fn');
 var e1fn = require('../common/e1fn');
 var e2fn = require('../common/e2fn');
@@ -2266,7 +2151,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Equidistant_Conic", "eqdc"];
 
-},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":7,"../common/e1fn":8,"../common/e2fn":9,"../common/e3fn":10,"../common/imlfn":12,"../common/mlfn":14,"../common/msfnz":15}],46:[function(require,module,exports){
+},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":8,"../common/e1fn":9,"../common/e2fn":10,"../common/e3fn":11,"../common/imlfn":13,"../common/mlfn":15,"../common/msfnz":16}],48:[function(require,module,exports){
 var FORTPI = Math.PI/4;
 var srat = require('../common/srat');
 var HALF_PI = Math.PI/2;
@@ -2313,7 +2198,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["gauss"];
 
-},{"../common/srat":22}],47:[function(require,module,exports){
+},{"../common/srat":23}],49:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var EPSLN = 1.0e-10;
 var asinz = require('../common/asinz');
@@ -2414,7 +2299,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["gnom"];
 
-},{"../common/adjust_lon":5,"../common/asinz":6}],48:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/asinz":7}],50:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 exports.init = function() {
   this.a = 6377397.155;
@@ -2514,7 +2399,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Krovak", "krovak"];
 
-},{"../common/adjust_lon":5}],49:[function(require,module,exports){
+},{"../common/adjust_lon":5}],51:[function(require,module,exports){
 var HALF_PI = Math.PI/2;
 var FORTPI = Math.PI/4;
 var EPSLN = 1.0e-10;
@@ -2804,7 +2689,7 @@ exports.authlat = function(beta, APA) {
 };
 exports.names = ["Lambert Azimuthal Equal Area", "Lambert_Azimuthal_Equal_Area", "laea"];
 
-},{"../common/adjust_lon":5,"../common/qsfnz":20}],50:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/qsfnz":21}],52:[function(require,module,exports){
 var EPSLN = 1.0e-10;
 var msfnz = require('../common/msfnz');
 var tsfnz = require('../common/tsfnz');
@@ -2941,7 +2826,7 @@ exports.inverse = function(p) {
 
 exports.names = ["Lambert Tangential Conformal Conic Projection", "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_2SP", "lcc"];
 
-},{"../common/adjust_lon":5,"../common/msfnz":15,"../common/phi2z":16,"../common/sign":21,"../common/tsfnz":24}],51:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/msfnz":16,"../common/phi2z":17,"../common/sign":22,"../common/tsfnz":25}],53:[function(require,module,exports){
 exports.init = function() {
   //no-op for longlat
 };
@@ -2953,7 +2838,7 @@ exports.forward = identity;
 exports.inverse = identity;
 exports.names = ["longlat", "identity"];
 
-},{}],52:[function(require,module,exports){
+},{}],54:[function(require,module,exports){
 var msfnz = require('../common/msfnz');
 var HALF_PI = Math.PI/2;
 var EPSLN = 1.0e-10;
@@ -3052,7 +2937,7 @@ exports.inverse = function(p) {
 
 exports.names = ["Mercator", "Popular Visualisation Pseudo Mercator", "Mercator_1SP", "Mercator_Auxiliary_Sphere", "merc"];
 
-},{"../common/adjust_lon":5,"../common/msfnz":15,"../common/phi2z":16,"../common/tsfnz":24}],53:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/msfnz":16,"../common/phi2z":17,"../common/tsfnz":25}],55:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 /*
   reference
@@ -3099,7 +2984,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Miller_Cylindrical", "mill"];
 
-},{"../common/adjust_lon":5}],54:[function(require,module,exports){
+},{"../common/adjust_lon":5}],56:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var EPSLN = 1.0e-10;
 exports.init = function() {};
@@ -3178,7 +3063,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["Mollweide", "moll"];
 
-},{"../common/adjust_lon":5}],55:[function(require,module,exports){
+},{"../common/adjust_lon":5}],57:[function(require,module,exports){
 var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
 /*
   reference
@@ -3398,7 +3283,7 @@ exports.inverse = function(p) {
   return p;
 };
 exports.names = ["New_Zealand_Map_Grid", "nzmg"];
-},{}],56:[function(require,module,exports){
+},{}],58:[function(require,module,exports){
 var tsfnz = require('../common/tsfnz');
 var adjust_lon = require('../common/adjust_lon');
 var phi2z = require('../common/phi2z');
@@ -3567,7 +3452,7 @@ exports.inverse = function(p) {
 };
 
 exports.names = ["Hotine_Oblique_Mercator", "Hotine Oblique Mercator", "Hotine_Oblique_Mercator_Azimuth_Natural_Origin", "Hotine_Oblique_Mercator_Azimuth_Center", "omerc"];
-},{"../common/adjust_lon":5,"../common/phi2z":16,"../common/tsfnz":24}],57:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/phi2z":17,"../common/tsfnz":25}],59:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var EPSLN = 1.0e-10;
 var asinz = require('../common/asinz');
@@ -3655,7 +3540,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["ortho"];
 
-},{"../common/adjust_lon":5,"../common/asinz":6}],58:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/asinz":7}],60:[function(require,module,exports){
 var e0fn = require('../common/e0fn');
 var e1fn = require('../common/e1fn');
 var e2fn = require('../common/e2fn');
@@ -3784,7 +3669,7 @@ exports.inverse = function(p) {
   return p;
 };
 exports.names = ["Polyconic", "poly"];
-},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":7,"../common/e1fn":8,"../common/e2fn":9,"../common/e3fn":10,"../common/gN":11,"../common/mlfn":14}],59:[function(require,module,exports){
+},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/e0fn":8,"../common/e1fn":9,"../common/e2fn":10,"../common/e3fn":11,"../common/gN":12,"../common/mlfn":15}],61:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var adjust_lat = require('../common/adjust_lat');
 var pj_enfn = require('../common/pj_enfn');
@@ -3891,7 +3776,7 @@ exports.inverse = function(p) {
   return p;
 };
 exports.names = ["Sinusoidal", "sinu"];
-},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/asinz":6,"../common/pj_enfn":17,"../common/pj_inv_mlfn":18,"../common/pj_mlfn":19}],60:[function(require,module,exports){
+},{"../common/adjust_lat":4,"../common/adjust_lon":5,"../common/asinz":7,"../common/pj_enfn":18,"../common/pj_inv_mlfn":19,"../common/pj_mlfn":20}],62:[function(require,module,exports){
 /*
   references:
     Formules et constantes pour le Calcul pour la
@@ -3973,7 +3858,7 @@ exports.inverse = function(p) {
 
 exports.names = ["somerc"];
 
-},{}],61:[function(require,module,exports){
+},{}],63:[function(require,module,exports){
 var HALF_PI = Math.PI/2;
 var EPSLN = 1.0e-10;
 var sign = require('../common/sign');
@@ -4141,7 +4026,7 @@ exports.inverse = function(p) {
 };
 exports.names = ["stere", "Stereographic_South_Pole", "Polar Stereographic (variant B)"];
 
-},{"../common/adjust_lon":5,"../common/msfnz":15,"../common/phi2z":16,"../common/sign":21,"../common/tsfnz":24}],62:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/msfnz":16,"../common/phi2z":17,"../common/sign":22,"../common/tsfnz":25}],64:[function(require,module,exports){
 var gauss = require('./gauss');
 var adjust_lon = require('../common/adjust_lon');
 exports.init = function() {
@@ -4200,24 +4085,28 @@ exports.inverse = function(p) {
 
 exports.names = ["Stereographic_North_Pole", "Oblique_Stereographic", "Polar_Stereographic", "sterea","Oblique Stereographic Alternative"];
 
-},{"../common/adjust_lon":5,"./gauss":46}],63:[function(require,module,exports){
-var e0fn = require('../common/e0fn');
-var e1fn = require('../common/e1fn');
-var e2fn = require('../common/e2fn');
-var e3fn = require('../common/e3fn');
-var mlfn = require('../common/mlfn');
+},{"../common/adjust_lon":5,"./gauss":48}],65:[function(require,module,exports){
+// Heavily based on this tmerc projection implementation
+// https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/tmerc.js
+
+var pj_enfn = require('../common/pj_enfn');
+var pj_mlfn = require('../common/pj_mlfn');
+var pj_inv_mlfn = require('../common/pj_inv_mlfn');
 var adjust_lon = require('../common/adjust_lon');
-var HALF_PI = Math.PI/2;
+var HALF_PI = Math.PI / 2;
 var EPSLN = 1.0e-10;
 var sign = require('../common/sign');
-var asinz = require('../common/asinz');
 
 exports.init = function() {
-  this.e0 = e0fn(this.es);
-  this.e1 = e1fn(this.es);
-  this.e2 = e2fn(this.es);
-  this.e3 = e3fn(this.es);
-  this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
+  this.x0 = this.x0 !== undefined ? this.x0 : 0;
+  this.y0 = this.y0 !== undefined ? this.y0 : 0;
+  this.long0 = this.long0 !== undefined ? this.long0 : 0;
+  this.lat0 = this.lat0 !== undefined ? this.lat0 : 0;
+
+  if (this.es) {
+    this.en = pj_enfn(this.es);
+    this.ml0 = pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
+  }
 };
 
 /**
@@ -4234,36 +4123,65 @@ exports.forward = function(p) {
   var sin_phi = Math.sin(lat);
   var cos_phi = Math.cos(lat);
 
-  if (this.sphere) {
+  if (!this.es) {
     var b = cos_phi * Math.sin(delta_lon);
-    if ((Math.abs(Math.abs(b) - 1)) < 0.0000000001) {
+
+    if ((Math.abs(Math.abs(b) - 1)) < EPSLN) {
       return (93);
     }
     else {
-      x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b));
-      con = Math.acos(cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - b * b));
+      x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b)) + this.x0;
+      y = cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - Math.pow(b, 2));
+      b = Math.abs(y);
+
+      if (b >= 1) {
+        if ((b - 1) > EPSLN) {
+          return (93);
+        }
+        else {
+          y = 0;
+        }
+      }
+      else {
+        y = Math.acos(y);
+      }
+
       if (lat < 0) {
-        con = -con;
+        y = -y;
       }
-      y = this.a * this.k0 * (con - this.lat0);
+
+      y = this.a * this.k0 * (y - this.lat0) + this.y0;
     }
   }
   else {
     var al = cos_phi * delta_lon;
     var als = Math.pow(al, 2);
     var c = this.ep2 * Math.pow(cos_phi, 2);
-    var tq = Math.tan(lat);
+    var cs = Math.pow(c, 2);
+    var tq = Math.abs(cos_phi) > EPSLN ? Math.tan(lat) : 0;
     var t = Math.pow(tq, 2);
+    var ts = Math.pow(t, 2);
     con = 1 - this.es * Math.pow(sin_phi, 2);
-    var n = this.a / Math.sqrt(con);
-    var ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, lat);
+    al = al / Math.sqrt(con);
+    var ml = pj_mlfn(lat, sin_phi, cos_phi, this.en);
 
-    x = this.k0 * n * al * (1 + als / 6 * (1 - t + c + als / 20 * (5 - 18 * t + Math.pow(t, 2) + 72 * c - 58 * this.ep2))) + this.x0;
-    y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24 * (5 - t + 9 * c + 4 * Math.pow(c, 2) + als / 30 * (61 - 58 * t + Math.pow(t, 2) + 600 * c - 330 * this.ep2))))) + this.y0;
+    x = this.a * (this.k0 * al * (1 +
+      als / 6 * (1 - t + c +
+      als / 20 * (5 - 18 * t + ts + 14 * c - 58 * t * c +
+      als / 42 * (61 + 179 * ts - ts * t - 479 * t))))) +
+      this.x0;
 
+    y = this.a * (this.k0 * (ml - this.ml0 +
+      sin_phi * delta_lon * al / 2 * (1 +
+      als / 12 * (5 - t + 9 * c + 4 * cs +
+      als / 30 * (61 + ts - 58 * t + 270 * c - 330 * t * c +
+      als / 56 * (1385 + 543 * ts - ts * t - 3111 * t)))))) +
+      this.y0;
   }
+
   p.x = x;
   p.y = y;
+
   return p;
 };
 
@@ -4272,81 +4190,84 @@ exports.forward = function(p) {
   */
 exports.inverse = function(p) {
   var con, phi;
-  var delta_phi;
-  var i;
-  var max_iter = 6;
   var lat, lon;
+  var x = (p.x - this.x0) * (1 / this.a);
+  var y = (p.y - this.y0) * (1 / this.a);
 
-  if (this.sphere) {
-    var f = Math.exp(p.x / (this.a * this.k0));
+  if (!this.es) {
+    var f = Math.exp(x / this.k0);
     var g = 0.5 * (f - 1 / f);
-    var temp = this.lat0 + p.y / (this.a * this.k0);
+    var temp = this.lat0 + y / this.k0;
     var h = Math.cos(temp);
-    con = Math.sqrt((1 - h * h) / (1 + g * g));
-    lat = asinz(con);
-    if (temp < 0) {
+    con = Math.sqrt((1 - Math.pow(h, 2)) / (1 + Math.pow(g, 2)));
+    lat = Math.asin(con);
+
+    if (y < 0) {
       lat = -lat;
     }
+
     if ((g === 0) && (h === 0)) {
-      lon = this.long0;
+      lon = 0;
     }
     else {
       lon = adjust_lon(Math.atan2(g, h) + this.long0);
     }
   }
   else { // ellipsoidal form
-    var x = p.x - this.x0;
-    var y = p.y - this.y0;
-
-    con = (this.ml0 + y / this.k0) / this.a;
-    phi = con;
-    for (i = 0; true; i++) {
-      delta_phi = ((con + this.e1 * Math.sin(2 * phi) - this.e2 * Math.sin(4 * phi) + this.e3 * Math.sin(6 * phi)) / this.e0) - phi;
-      phi += delta_phi;
-      if (Math.abs(delta_phi) <= EPSLN) {
-        break;
-      }
-      if (i >= max_iter) {
-        return (95);
-      }
-    } // for()
+    con = this.ml0 + y / this.k0;
+    phi = pj_inv_mlfn(con, this.es, this.en);
+
     if (Math.abs(phi) < HALF_PI) {
       var sin_phi = Math.sin(phi);
       var cos_phi = Math.cos(phi);
-      var tan_phi = Math.tan(phi);
+      var tan_phi = Math.abs(cos_phi) > EPSLN ? Math.tan(phi) : 0;
       var c = this.ep2 * Math.pow(cos_phi, 2);
       var cs = Math.pow(c, 2);
       var t = Math.pow(tan_phi, 2);
       var ts = Math.pow(t, 2);
       con = 1 - this.es * Math.pow(sin_phi, 2);
-      var n = this.a / Math.sqrt(con);
-      var r = n * (1 - this.es) / con;
-      var d = x / (n * this.k0);
+      var d = x * Math.sqrt(con) / this.k0;
       var ds = Math.pow(d, 2);
-      lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24 * (5 + 3 * t + 10 * c - 4 * cs - 9 * this.ep2 - ds / 30 * (61 + 90 * t + 298 * c + 45 * ts - 252 * this.ep2 - 3 * cs)));
-      lon = adjust_lon(this.long0 + (d * (1 - ds / 6 * (1 + 2 * t + c - ds / 20 * (5 - 2 * c + 28 * t - 3 * cs + 8 * this.ep2 + 24 * ts))) / cos_phi));
+      con = con * tan_phi;
+
+      lat = phi - (con * ds / (1 - this.es)) * 0.5 * (1 -
+        ds / 12 * (5 + 3 * t - 9 * c * t + c - 4 * cs -
+        ds / 30 * (61 + 90 * t - 252 * c * t + 45 * ts + 46 * c -
+        ds / 56 * (1385 + 3633 * t + 4095 * ts + 1574 * ts * t))));
+
+      lon = adjust_lon(this.long0 + (d * (1 -
+        ds / 6 * (1 + 2 * t + c -
+        ds / 20 * (5 + 28 * t + 24 * ts + 8 * c * t + 6 * c -
+        ds / 42 * (61 + 662 * t + 1320 * ts + 720 * ts * t)))) / cos_phi));
     }
     else {
       lat = HALF_PI * sign(y);
-      lon = this.long0;
+      lon = 0;
     }
   }
+
   p.x = lon;
   p.y = lat;
+
   return p;
 };
+
 exports.names = ["Transverse_Mercator", "Transverse Mercator", "tmerc"];
 
-},{"../common/adjust_lon":5,"../common/asinz":6,"../common/e0fn":7,"../common/e1fn":8,"../common/e2fn":9,"../common/e3fn":10,"../common/mlfn":14,"../common/sign":21}],64:[function(require,module,exports){
-var D2R = 0.01745329251994329577;
+},{"../common/adjust_lon":5,"../common/pj_enfn":18,"../common/pj_inv_mlfn":19,"../common/pj_mlfn":20,"../common/sign":22}],66:[function(require,module,exports){
+var adjust_zone = require('../common/adjust_zone');
 var tmerc = require('./tmerc');
+
 exports.dependsOn = 'tmerc';
+
 exports.init = function() {
-  if (!this.zone) {
+  var zone = adjust_zone(this.zone, this.long0);
+  if (!zone) {
     return;
   }
+
   this.lat0 = 0;
-  this.long0 = ((6 * Math.abs(this.zone)) - 183) * D2R;
+  this.long0 = (zone + 0.5) * Math.PI / 30 - Math.PI;
   this.x0 = 500000;
   this.y0 = this.utmSouth ? 10000000 : 0;
   this.k0 = 0.9996;
@@ -4355,9 +4276,10 @@ exports.init = function() {
   this.forward = tmerc.forward;
   this.inverse = tmerc.inverse;
 };
+
 exports.names = ["Universal Transverse Mercator System", "utm"];
 
-},{"./tmerc":63}],65:[function(require,module,exports){
+},{"../common/adjust_zone":6,"./tmerc":65}],67:[function(require,module,exports){
 var adjust_lon = require('../common/adjust_lon');
 var HALF_PI = Math.PI/2;
 var EPSLN = 1.0e-10;
@@ -4478,7 +4400,7 @@ exports.inverse = function(p) {
   return p;
 };
 exports.names = ["Van_der_Grinten_I", "VanDerGrinten", "vandg"];
-},{"../common/adjust_lon":5,"../common/asinz":6}],66:[function(require,module,exports){
+},{"../common/adjust_lon":5,"../common/asinz":7}],68:[function(require,module,exports){
 var D2R = 0.01745329251994329577;
 var R2D = 57.29577951308232088;
 var PJD_3PARAM = 1;
@@ -4487,36 +4409,40 @@ var datum_transform = require('./datum_transform');
 var adjust_axis = require('./adjust_axis');
 var proj = require('./Proj');
 var toPoint = require('./common/toPoint');
+function checkNotWGS(source, dest) {
+  return ((source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM) && dest.datumCode !== 'WGS84') || ((dest.datum.datum_type === PJD_3PARAM || dest.datum.datum_type === PJD_7PARAM) && source.datumCode !== 'WGS84');
+}
 module.exports = function transform(source, dest, point) {
   var wgs84;
   if (Array.isArray(point)) {
     point = toPoint(point);
   }
-  function checkNotWGS(source, dest) {
-    return ((source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM) && dest.datumCode !== "WGS84");
-  }
 
   // Workaround for datum shifts towgs84, if either source or destination projection is not wgs84
-  if (source.datum && dest.datum && (checkNotWGS(source, dest) || checkNotWGS(dest, source))) {
+  if (source.datum && dest.datum && checkNotWGS(source, dest)) {
     wgs84 = new proj('WGS84');
-    transform(source, wgs84, point);
+    point = transform(source, wgs84, point);
     source = wgs84;
   }
   // DGR, 2010/11/12
-  if (source.axis !== "enu") {
-    adjust_axis(source, false, point);
+  if (source.axis !== 'enu') {
+    point = adjust_axis(source, false, point);
   }
   // Transform source points to long/lat, if they aren't already.
-  if (source.projName === "longlat") {
-    point.x *= D2R; // convert degrees to radians
-    point.y *= D2R;
+  if (source.projName === 'longlat') {
+    point = {
+      x: point.x * D2R,
+      y: point.y * D2R
+    };
   }
   else {
     if (source.to_meter) {
-      point.x *= source.to_meter;
-      point.y *= source.to_meter;
+      point = {
+        x: point.x * source.to_meter,
+        y: point.y * source.to_meter
+      };
     }
-    source.inverse(point); // Convert Cartesian to longlat
+    point = source.inverse(point); // Convert Cartesian to longlat
   }
   // Adjust for the prime meridian if necessary
   if (source.from_greenwich) {
@@ -4528,30 +4454,37 @@ module.exports = function transform(source, dest, point) {
 
   // Adjust for the prime meridian if necessary
   if (dest.from_greenwich) {
-    point.x -= dest.from_greenwich;
+    point = {
+      x: point.x - dest.grom_greenwich,
+      y: point.y
+    };
   }
 
-  if (dest.projName === "longlat") {
+  if (dest.projName === 'longlat') {
     // convert radians to decimal degrees
-    point.x *= R2D;
-    point.y *= R2D;
-  }
-  else { // else project
-    dest.forward(point);
+    point = {
+      x: point.x * R2D,
+      y: point.y * R2D
+    };
+  } else { // else project
+    point = dest.forward(point);
     if (dest.to_meter) {
-      point.x /= dest.to_meter;
-      point.y /= dest.to_meter;
+      point = {
+        x: point.x / dest.to_meter,
+        y: point.y / dest.to_meter
+      };
     }
   }
 
   // DGR, 2010/11/12
-  if (dest.axis !== "enu") {
-    adjust_axis(dest, true, point);
+  if (dest.axis !== 'enu') {
+    return adjust_axis(dest, true, point);
   }
 
   return point;
 };
-},{"./Proj":2,"./adjust_axis":3,"./common/toPoint":23,"./datum_transform":31}],67:[function(require,module,exports){
+
+},{"./Proj":2,"./adjust_axis":3,"./common/toPoint":24,"./datum_transform":33}],69:[function(require,module,exports){
 var D2R = 0.01745329251994329577;
 var extend = require('./extend');
 
@@ -4776,7 +4709,7 @@ module.exports = function(wkt, self) {
   return extend(self, obj.output);
 };
 
-},{"./extend":34}],68:[function(require,module,exports){
+},{"./extend":36}],70:[function(require,module,exports){
 
 
 
@@ -5520,52 +5453,5 @@ function getMinNorthing(zoneLetter) {
 
 }
 
-},{}],69:[function(require,module,exports){
-module.exports={
-  "name": "proj4",
-  "version": "2.3.15",
-  "description": "Proj4js is a JavaScript library to transform point coordinates from one coordinate system to another, including datum transformations.",
-  "main": "lib/index.js",
-  "directories": {
-    "test": "test",
-    "doc": "docs"
-  },
-  "scripts": {
-    "test": "./node_modules/istanbul/lib/cli.js test ./node_modules/mocha/bin/_mocha test/test.js"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/proj4js/proj4js.git"
-  },
-  "author": "",
-  "license": "MIT",
-  "jam": {
-    "main": "dist/proj4.js",
-    "include": [
-      "dist/proj4.js",
-      "README.md",
-      "AUTHORS",
-      "LICENSE.md"
-    ]
-  },
-  "devDependencies": {
-    "grunt-cli": "~0.1.13",
-    "grunt": "~0.4.2",
-    "grunt-contrib-connect": "~0.6.0",
-    "grunt-contrib-jshint": "~0.8.0",
-    "chai": "~1.8.1",
-    "mocha": "~1.17.1",
-    "grunt-mocha-phantomjs": "~0.4.0",
-    "browserify": "~12.0.1",
-    "grunt-browserify": "~4.0.1",
-    "grunt-contrib-uglify": "~0.11.1",
-    "curl": "git://github.com/cujojs/curl.git",
-    "istanbul": "~0.2.4",
-    "tin": "~0.4.0"
-  },
-  "dependencies": {
-    "mgrs": "~0.0.2"
-  }
-}
-},{}]},{},[36])(36)
+},{}]},{},[38])(38)
 });
\ No newline at end of file
diff --git a/dist/test.html b/dist/test.html
deleted file mode 100644
index 89f04d8..0000000
--- a/dist/test.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-<body>
-  <div id="result"></div>
-  <script src="proj4-src.js"></script>
-  <script>
-  document.getElementById('result').innerHTML =
-      proj4('EPSG:4326', 'EPSG:3857', [16, 48]);
-  </script>
-</body>
-</html>
-
diff --git a/lib/Proj.js b/lib/Proj.js
index f4a55bd..7bc65db 100644
--- a/lib/Proj.js
+++ b/lib/Proj.js
@@ -1,7 +1,10 @@
-var parseCode = require("./parseCode");
+var parseCode = require('./parseCode');
 var extend = require('./extend');
 var projections = require('./projections');
 var deriveConstants = require('./deriveConstants');
+var Datum = require('./constants/Datum');
+var datum = require('./datum');
+
 
 function Projection(srsCode,callback) {
   if (!(this instanceof Projection)) {
@@ -17,16 +20,49 @@ function Projection(srsCode,callback) {
     callback(srsCode);
     return;
   }
-  var modifiedJSON = deriveConstants(json);
-  var ourProj = Projection.projections.get(modifiedJSON.projName);
-  if(ourProj){
-    extend(this, modifiedJSON);
-    extend(this, ourProj);
-    this.init();
-    callback(null, this);
-  }else{
+  var ourProj = Projection.projections.get(json.projName);
+  if(!ourProj){
     callback(srsCode);
+    return;
+  }
+  if (json.datumCode && json.datumCode !== 'none') {
+    var datumDef = Datum[json.datumCode];
+    if (datumDef) {
+      json.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
+      json.ellps = datumDef.ellipse;
+      json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode;
+    }
   }
+  json.k0 = json.k0 || 1.0;
+  json.axis = json.axis || 'enu';
+
+  var sphere = deriveConstants.sphere(json.a, json.b, json.rf, json.ellps, json.sphere);
+  var ecc = deriveConstants.eccentricity(sphere.a, sphere.b, sphere.rf, json.R_A);
+  var datumObj = json.datum || datum(json.datumCode, json.datum_params, sphere.a, sphere.b, ecc.es, ecc.ep2);
+
+  extend(this, json); // transfer everything over from the projection because we don't know what we'll need
+  extend(this, ourProj); // transfer all the methods from the projection
+
+  // copy the 4 things over we calulated in deriveConstants.sphere
+  this.a = sphere.a;
+  this.b = sphere.b;
+  this.rf = sphere.rf;
+  this.sphere = sphere.sphere;
+
+  // copy the 3 things we calculated in deriveConstants.eccentricity
+  this.es = ecc.es;
+  this.e = ecc.e;
+  this.ep2 = ecc.ep2;
+
+  // add in the datum object
+  this.datum = datumObj;
+
+  // init the projection
+  this.init();
+
+  // legecy callback from back in the day when it went to spatialreference.org
+  callback(null, this);
+
 }
 Projection.projections = projections;
 Projection.projections.start();
diff --git a/lib/adjust_axis.js b/lib/adjust_axis.js
index 1690be1..8a84296 100644
--- a/lib/adjust_axis.js
+++ b/lib/adjust_axis.js
@@ -3,6 +3,7 @@ module.exports = function(crs, denorm, point) {
     yin = point.y,
     zin = point.z || 0.0;
   var v, t, i;
+  var out = {};
   for (i = 0; i < 3; i++) {
     if (denorm && i === 2 && point.z === undefined) {
       continue;
@@ -21,25 +22,25 @@ module.exports = function(crs, denorm, point) {
     }
     switch (crs.axis[i]) {
     case 'e':
-      point[t] = v;
+      out[t] = v;
       break;
     case 'w':
-      point[t] = -v;
+      out[t] = -v;
       break;
     case 'n':
-      point[t] = v;
+      out[t] = v;
       break;
     case 's':
-      point[t] = -v;
+      out[t] = -v;
       break;
     case 'u':
       if (point[t] !== undefined) {
-        point.z = v;
+        out.z = v;
       }
       break;
     case 'd':
       if (point[t] !== undefined) {
-        point.z = -v;
+        out.z = -v;
       }
       break;
     default:
@@ -47,5 +48,5 @@ module.exports = function(crs, denorm, point) {
       return null;
     }
   }
-  return point;
+  return out;
 };
diff --git a/lib/common/adjust_zone.js b/lib/common/adjust_zone.js
new file mode 100644
index 0000000..861773b
--- /dev/null
+++ b/lib/common/adjust_zone.js
@@ -0,0 +1,19 @@
+var adjust_lon = require('./adjust_lon');
+
+module.exports = function(zone, lon) {
+  if (!zone) {
+    zone = Math.floor((adjust_lon(lon) + Math.PI) * 30 / Math.PI);
+
+    if (zone < 0) {
+      return 0;
+    }
+    else if (zone >= 60) {
+      return 59;
+    }
+  }
+  else {
+    if (zone > 0 && zone <= 60) {
+      return zone - 1;
+    }
+  }
+};
diff --git a/lib/datum.js b/lib/datum.js
index 40d3328..a7391c5 100644
--- a/lib/datum.js
+++ b/lib/datum.js
@@ -1,403 +1,38 @@
-var HALF_PI = Math.PI/2;
 var PJD_3PARAM = 1;
 var PJD_7PARAM = 2;
-var PJD_GRIDSHIFT = 3;
 var PJD_WGS84 = 4; // WGS84 or equivalent
 var PJD_NODATUM = 5; // WGS84 or equivalent
 var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
-var AD_C = 1.0026000;
-var COS_67P5 = 0.38268343236508977;
-var datum = function(proj) {
-  if (!(this instanceof datum)) {
-    return new datum(proj);
-  }
-  this.datum_type = PJD_WGS84; //default setting
-  if (!proj) {
-    return;
-  }
-  if (proj.datumCode && proj.datumCode === 'none') {
-    this.datum_type = PJD_NODATUM;
-  }
-
-  if (proj.datum_params) {
-    this.datum_params = proj.datum_params.map(parseFloat);
-    if (this.datum_params[0] !== 0 || this.datum_params[1] !== 0 || this.datum_params[2] !== 0) {
-      this.datum_type = PJD_3PARAM;
-    }
-    if (this.datum_params.length > 3) {
-      if (this.datum_params[3] !== 0 || this.datum_params[4] !== 0 || this.datum_params[5] !== 0 || this.datum_params[6] !== 0) {
-        this.datum_type = PJD_7PARAM;
-        this.datum_params[3] *= SEC_TO_RAD;
-        this.datum_params[4] *= SEC_TO_RAD;
-        this.datum_params[5] *= SEC_TO_RAD;
-        this.datum_params[6] = (this.datum_params[6] / 1000000.0) + 1.0;
-      }
-    }
-  }
-
-  // DGR 2011-03-21 : nadgrids support
-  this.datum_type = proj.grids ? PJD_GRIDSHIFT : this.datum_type;
 
-  this.a = proj.a; //datum object also uses these values
-  this.b = proj.b;
-  this.es = proj.es;
-  this.ep2 = proj.ep2;
-  if (this.datum_type === PJD_GRIDSHIFT) {
-    this.grids = proj.grids;
+function datum(datumCode, datum_params, a, b, es, ep2) {
+  var out = {};
+  out.datum_type = PJD_WGS84; //default setting
+  if (datumCode && datumCode === 'none') {
+    out.datum_type = PJD_NODATUM;
   }
-};
-datum.prototype = {
-
-
-  /****************************************************************/
-  // cs_compare_datums()
-  //   Returns TRUE if the two datums match, otherwise FALSE.
-  compare_datums: function(dest) {
-    if (this.datum_type !== dest.datum_type) {
-      return false; // false, datums are not equal
-    }
-    else if (this.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
-      // the tolerence for es is to ensure that GRS80 and WGS84
-      // are considered identical
-      return false;
-    }
-    else if (this.datum_type === PJD_3PARAM) {
-      return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2]);
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      return (this.datum_params[0] === dest.datum_params[0] && this.datum_params[1] === dest.datum_params[1] && this.datum_params[2] === dest.datum_params[2] && this.datum_params[3] === dest.datum_params[3] && this.datum_params[4] === dest.datum_params[4] && this.datum_params[5] === dest.datum_params[5] && this.datum_params[6] === dest.datum_params[6]);
-    }
-    else if (this.datum_type === PJD_GRIDSHIFT || dest.datum_type === PJD_GRIDSHIFT) {
-      //alert("ERROR: Grid shift transformations are not implemented.");
-      //return false
-      //DGR 2012-07-29 lazy ...
-      return this.nadgrids === dest.nadgrids;
-    }
-    else {
-      return true; // datums are equal
-    }
-  }, // cs_compare_datums()
-
-  /*
-   * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
-   * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
-   * according to the current ellipsoid parameters.
-   *
-   *    Latitude  : Geodetic latitude in radians                     (input)
-   *    Longitude : Geodetic longitude in radians                    (input)
-   *    Height    : Geodetic height, in meters                       (input)
-   *    X         : Calculated Geocentric X coordinate, in meters    (output)
-   *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
-   *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
-   *
-   */
-  geodetic_to_geocentric: function(p) {
-    var Longitude = p.x;
-    var Latitude = p.y;
-    var Height = p.z ? p.z : 0; //Z value not always supplied
-    var X; // output
-    var Y;
-    var Z;
-
-    var Error_Code = 0; //  GEOCENT_NO_ERROR;
-    var Rn; /*  Earth radius at location  */
-    var Sin_Lat; /*  Math.sin(Latitude)  */
-    var Sin2_Lat; /*  Square of Math.sin(Latitude)  */
-    var Cos_Lat; /*  Math.cos(Latitude)  */
-
-    /*
-     ** Don't blow up if Latitude is just a little out of the value
-     ** range as it may just be a rounding issue.  Also removed longitude
-     ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
-     */
-    if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
-      Latitude = -HALF_PI;
-    }
-    else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
-      Latitude = HALF_PI;
-    }
-    else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
-      /* Latitude out of range */
-      //..reportError('geocent:lat out of range:' + Latitude);
-      return null;
-    }
-
-    if (Longitude > Math.PI) {
-      Longitude -= (2 * Math.PI);
-    }
-    Sin_Lat = Math.sin(Latitude);
-    Cos_Lat = Math.cos(Latitude);
-    Sin2_Lat = Sin_Lat * Sin_Lat;
-    Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
-    X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
-    Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
-    Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
-
-    p.x = X;
-    p.y = Y;
-    p.z = Z;
-    return Error_Code;
-  }, // cs_geodetic_to_geocentric()
-
-
-  geocentric_to_geodetic: function(p) {
-    /* local defintions and variables */
-    /* end-criterium of loop, accuracy of sin(Latitude) */
-    var genau = 1e-12;
-    var genau2 = (genau * genau);
-    var maxiter = 30;
-
-    var P; /* distance between semi-minor axis and location */
-    var RR; /* distance between center and location */
-    var CT; /* sin of geocentric latitude */
-    var ST; /* cos of geocentric latitude */
-    var RX;
-    var RK;
-    var RN; /* Earth radius at location */
-    var CPHI0; /* cos of start or old geodetic latitude in iterations */
-    var SPHI0; /* sin of start or old geodetic latitude in iterations */
-    var CPHI; /* cos of searched geodetic latitude */
-    var SPHI; /* sin of searched geodetic latitude */
-    var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
-    var At_Pole; /* indicates location is in polar region */
-    var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
-
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0.0; //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    At_Pole = false;
-    P = Math.sqrt(X * X + Y * Y);
-    RR = Math.sqrt(X * X + Y * Y + Z * Z);
-
-    /*      special cases for latitude and longitude */
-    if (P / this.a < genau) {
-
-      /*  special case, if P=0. (X=0., Y=0.) */
-      At_Pole = true;
-      Longitude = 0.0;
 
-      /*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
-       *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
-      if (RR / this.a < genau) {
-        Latitude = HALF_PI;
-        Height = -this.b;
-        return;
+  if (datum_params) {
+    out.datum_params = datum_params.map(parseFloat);
+    if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) {
+      out.datum_type = PJD_3PARAM;
+    }
+    if (out.datum_params.length > 3) {
+      if (out.datum_params[3] !== 0 || out.datum_params[4] !== 0 || out.datum_params[5] !== 0 || out.datum_params[6] !== 0) {
+        out.datum_type = PJD_7PARAM;
+        out.datum_params[3] *= SEC_TO_RAD;
+        out.datum_params[4] *= SEC_TO_RAD;
+        out.datum_params[5] *= SEC_TO_RAD;
+        out.datum_params[6] = (out.datum_params[6] / 1000000.0) + 1.0;
       }
     }
-    else {
-      /*  ellipsoidal (geodetic) longitude
-       *  interval: -PI < Longitude <= +PI */
-      Longitude = Math.atan2(Y, X);
-    }
-
-    /* --------------------------------------------------------------
-     * Following iterative algorithm was developped by
-     * "Institut for Erdmessung", University of Hannover, July 1988.
-     * Internet: www.ife.uni-hannover.de
-     * Iterative computation of CPHI,SPHI and Height.
-     * Iteration of CPHI and SPHI to 10**-12 radian resp.
-     * 2*10**-7 arcsec.
-     * --------------------------------------------------------------
-     */
-    CT = Z / RR;
-    ST = P / RR;
-    RX = 1.0 / Math.sqrt(1.0 - this.es * (2.0 - this.es) * ST * ST);
-    CPHI0 = ST * (1.0 - this.es) * RX;
-    SPHI0 = CT * RX;
-    iter = 0;
-
-    /* loop to find sin(Latitude) resp. Latitude
-     * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
-    do {
-      iter++;
-      RN = this.a / Math.sqrt(1.0 - this.es * SPHI0 * SPHI0);
-
-      /*  ellipsoidal (geodetic) height */
-      Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - this.es * SPHI0 * SPHI0);
-
-      RK = this.es * RN / (RN + Height);
-      RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
-      CPHI = ST * (1.0 - RK) * RX;
-      SPHI = CT * RX;
-      SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
-      CPHI0 = CPHI;
-      SPHI0 = SPHI;
-    }
-    while (SDPHI * SDPHI > genau2 && iter < maxiter);
-
-    /*      ellipsoidal (geodetic) latitude */
-    Latitude = Math.atan(SPHI / Math.abs(CPHI));
-
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // cs_geocentric_to_geodetic()
-
-  /** Convert_Geocentric_To_Geodetic
-   * The method used here is derived from 'An Improved Algorithm for
-   * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
-   */
-  geocentric_to_geodetic_noniter: function(p) {
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0; //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    var W; /* distance from Z axis */
-    var W2; /* square of distance from Z axis */
-    var T0; /* initial estimate of vertical component */
-    var T1; /* corrected estimate of vertical component */
-    var S0; /* initial estimate of horizontal component */
-    var S1; /* corrected estimate of horizontal component */
-    var Sin_B0; /* Math.sin(B0), B0 is estimate of Bowring aux variable */
-    var Sin3_B0; /* cube of Math.sin(B0) */
-    var Cos_B0; /* Math.cos(B0) */
-    var Sin_p1; /* Math.sin(phi1), phi1 is estimated latitude */
-    var Cos_p1; /* Math.cos(phi1) */
-    var Rn; /* Earth radius at location */
-    var Sum; /* numerator of Math.cos(phi1) */
-    var At_Pole; /* indicates location is in polar region */
-
-    X = parseFloat(X); // cast from string to float
-    Y = parseFloat(Y);
-    Z = parseFloat(Z);
-
-    At_Pole = false;
-    if (X !== 0.0) {
-      Longitude = Math.atan2(Y, X);
-    }
-    else {
-      if (Y > 0) {
-        Longitude = HALF_PI;
-      }
-      else if (Y < 0) {
-        Longitude = -HALF_PI;
-      }
-      else {
-        At_Pole = true;
-        Longitude = 0.0;
-        if (Z > 0.0) { /* north pole */
-          Latitude = HALF_PI;
-        }
-        else if (Z < 0.0) { /* south pole */
-          Latitude = -HALF_PI;
-        }
-        else { /* center of earth */
-          Latitude = HALF_PI;
-          Height = -this.b;
-          return;
-        }
-      }
-    }
-    W2 = X * X + Y * Y;
-    W = Math.sqrt(W2);
-    T0 = Z * AD_C;
-    S0 = Math.sqrt(T0 * T0 + W2);
-    Sin_B0 = T0 / S0;
-    Cos_B0 = W / S0;
-    Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
-    T1 = Z + this.b * this.ep2 * Sin3_B0;
-    Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
-    S1 = Math.sqrt(T1 * T1 + Sum * Sum);
-    Sin_p1 = T1 / S1;
-    Cos_p1 = Sum / S1;
-    Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
-    if (Cos_p1 >= COS_67P5) {
-      Height = W / Cos_p1 - Rn;
-    }
-    else if (Cos_p1 <= -COS_67P5) {
-      Height = W / -Cos_p1 - Rn;
-    }
-    else {
-      Height = Z / Sin_p1 + Rn * (this.es - 1.0);
-    }
-    if (At_Pole === false) {
-      Latitude = Math.atan(Sin_p1 / Cos_p1);
-    }
-
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // geocentric_to_geodetic_noniter()
-
-  /****************************************************************/
-  // pj_geocentic_to_wgs84( p )
-  //  p = point to transform in geocentric coordinates (x,y,z)
-  geocentric_to_wgs84: function(p) {
-
-    if (this.datum_type === PJD_3PARAM) {
-      // if( x[io] === HUGE_VAL )
-      //    continue;
-      p.x += this.datum_params[0];
-      p.y += this.datum_params[1];
-      p.z += this.datum_params[2];
-
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      var Dx_BF = this.datum_params[0];
-      var Dy_BF = this.datum_params[1];
-      var Dz_BF = this.datum_params[2];
-      var Rx_BF = this.datum_params[3];
-      var Ry_BF = this.datum_params[4];
-      var Rz_BF = this.datum_params[5];
-      var M_BF = this.datum_params[6];
-      // if( x[io] === HUGE_VAL )
-      //    continue;
-      var x_out = M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF;
-      var y_out = M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF;
-      var z_out = M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF;
-      p.x = x_out;
-      p.y = y_out;
-      p.z = z_out;
-    }
-  }, // cs_geocentric_to_wgs84
-
-  /****************************************************************/
-  // pj_geocentic_from_wgs84()
-  //  coordinate system definition,
-  //  point to transform in geocentric coordinates (x,y,z)
-  geocentric_from_wgs84: function(p) {
-
-    if (this.datum_type === PJD_3PARAM) {
-      //if( x[io] === HUGE_VAL )
-      //    continue;
-      p.x -= this.datum_params[0];
-      p.y -= this.datum_params[1];
-      p.z -= this.datum_params[2];
+  }
 
-    }
-    else if (this.datum_type === PJD_7PARAM) {
-      var Dx_BF = this.datum_params[0];
-      var Dy_BF = this.datum_params[1];
-      var Dz_BF = this.datum_params[2];
-      var Rx_BF = this.datum_params[3];
-      var Ry_BF = this.datum_params[4];
-      var Rz_BF = this.datum_params[5];
-      var M_BF = this.datum_params[6];
-      var x_tmp = (p.x - Dx_BF) / M_BF;
-      var y_tmp = (p.y - Dy_BF) / M_BF;
-      var z_tmp = (p.z - Dz_BF) / M_BF;
-      //if( x[io] === HUGE_VAL )
-      //    continue;
 
-      p.x = x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp;
-      p.y = -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp;
-      p.z = Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp;
-    } //cs_geocentric_from_wgs84()
-  }
-};
+  out.a = a; //datum object also uses these values
+  out.b = b;
+  out.es = es;
+  out.ep2 = ep2;
+  return out;
+}
 
-/** point object, nothing fancy, just allows values to be
-    passed back and forth by reference rather than by value.
-    Other point classes may be used as long as they have
-    x and y properties, which will get modified in the transform method.
-*/
 module.exports = datum;
diff --git a/lib/datumUtils.js b/lib/datumUtils.js
new file mode 100644
index 0000000..d5a257e
--- /dev/null
+++ b/lib/datumUtils.js
@@ -0,0 +1,247 @@
+'use strict';
+var PJD_3PARAM = 1;
+var PJD_7PARAM = 2;
+var HALF_PI = Math.PI/2;
+
+exports.compareDatums = function(source, dest) {
+  if (source.datum_type !== dest.datum_type) {
+    return false; // false, datums are not equal
+  } else if (source.a !== dest.a || Math.abs(this.es - dest.es) > 0.000000000050) {
+    // the tolerence for es is to ensure that GRS80 and WGS84
+    // are considered identical
+    return false;
+  } else if (source.datum_type === PJD_3PARAM) {
+    return (this.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2]);
+  } else if (source.datum_type === PJD_7PARAM) {
+    return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2] && source.datum_params[3] === dest.datum_params[3] && source.datum_params[4] === dest.datum_params[4] && source.datum_params[5] === dest.datum_params[5] && source.datum_params[6] === dest.datum_params[6]);
+  } else {
+    return true; // datums are equal
+  }
+}; // cs_compare_datums()
+
+/*
+ * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
+ * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
+ * according to the current ellipsoid parameters.
+ *
+ *    Latitude  : Geodetic latitude in radians                     (input)
+ *    Longitude : Geodetic longitude in radians                    (input)
+ *    Height    : Geodetic height, in meters                       (input)
+ *    X         : Calculated Geocentric X coordinate, in meters    (output)
+ *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
+ *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
+ *
+ */
+exports.geodeticToGeocentric = function(p, es, a) {
+  var Longitude = p.x;
+  var Latitude = p.y;
+  var Height = p.z ? p.z : 0; //Z value not always supplied
+
+  var Rn; /*  Earth radius at location  */
+  var Sin_Lat; /*  Math.sin(Latitude)  */
+  var Sin2_Lat; /*  Square of Math.sin(Latitude)  */
+  var Cos_Lat; /*  Math.cos(Latitude)  */
+
+  /*
+   ** Don't blow up if Latitude is just a little out of the value
+   ** range as it may just be a rounding issue.  Also removed longitude
+   ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
+   */
+  if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) {
+    Latitude = -HALF_PI;
+  } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) {
+    Latitude = HALF_PI;
+  } else if ((Latitude < -HALF_PI) || (Latitude > HALF_PI)) {
+    /* Latitude out of range */
+    //..reportError('geocent:lat out of range:' + Latitude);
+    return null;
+  }
+
+  if (Longitude > Math.PI) {
+    Longitude -= (2 * Math.PI);
+  }
+  Sin_Lat = Math.sin(Latitude);
+  Cos_Lat = Math.cos(Latitude);
+  Sin2_Lat = Sin_Lat * Sin_Lat;
+  Rn = a / (Math.sqrt(1.0e0 - es * Sin2_Lat));
+  return {
+    x: (Rn + Height) * Cos_Lat * Math.cos(Longitude),
+    y: (Rn + Height) * Cos_Lat * Math.sin(Longitude),
+    z: ((Rn * (1 - es)) + Height) * Sin_Lat
+  };
+}; // cs_geodetic_to_geocentric()
+
+
+exports.geocentricToGeodetic = function(p, es, a, b) {
+  /* local defintions and variables */
+  /* end-criterium of loop, accuracy of sin(Latitude) */
+  var genau = 1e-12;
+  var genau2 = (genau * genau);
+  var maxiter = 30;
+
+  var P; /* distance between semi-minor axis and location */
+  var RR; /* distance between center and location */
+  var CT; /* sin of geocentric latitude */
+  var ST; /* cos of geocentric latitude */
+  var RX;
+  var RK;
+  var RN; /* Earth radius at location */
+  var CPHI0; /* cos of start or old geodetic latitude in iterations */
+  var SPHI0; /* sin of start or old geodetic latitude in iterations */
+  var CPHI; /* cos of searched geodetic latitude */
+  var SPHI; /* sin of searched geodetic latitude */
+  var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
+  var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */
+
+  var X = p.x;
+  var Y = p.y;
+  var Z = p.z ? p.z : 0.0; //Z value not always supplied
+  var Longitude;
+  var Latitude;
+  var Height;
+
+  P = Math.sqrt(X * X + Y * Y);
+  RR = Math.sqrt(X * X + Y * Y + Z * Z);
+
+  /*      special cases for latitude and longitude */
+  if (P / a < genau) {
+
+    /*  special case, if P=0. (X=0., Y=0.) */
+    Longitude = 0.0;
+
+    /*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
+     *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
+    if (RR / a < genau) {
+      Latitude = HALF_PI;
+      Height = -b;
+      return {
+        x: p.x,
+        y: p.y,
+        z: p.z
+      };
+    }
+  } else {
+    /*  ellipsoidal (geodetic) longitude
+     *  interval: -PI < Longitude <= +PI */
+    Longitude = Math.atan2(Y, X);
+  }
+
+  /* --------------------------------------------------------------
+   * Following iterative algorithm was developped by
+   * "Institut for Erdmessung", University of Hannover, July 1988.
+   * Internet: www.ife.uni-hannover.de
+   * Iterative computation of CPHI,SPHI and Height.
+   * Iteration of CPHI and SPHI to 10**-12 radian resp.
+   * 2*10**-7 arcsec.
+   * --------------------------------------------------------------
+   */
+  CT = Z / RR;
+  ST = P / RR;
+  RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST);
+  CPHI0 = ST * (1.0 - es) * RX;
+  SPHI0 = CT * RX;
+  iter = 0;
+
+  /* loop to find sin(Latitude) resp. Latitude
+   * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
+  do {
+    iter++;
+    RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0);
+
+    /*  ellipsoidal (geodetic) height */
+    Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0);
+
+    RK = es * RN / (RN + Height);
+    RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST);
+    CPHI = ST * (1.0 - RK) * RX;
+    SPHI = CT * RX;
+    SDPHI = SPHI * CPHI0 - CPHI * SPHI0;
+    CPHI0 = CPHI;
+    SPHI0 = SPHI;
+  }
+  while (SDPHI * SDPHI > genau2 && iter < maxiter);
+
+  /*      ellipsoidal (geodetic) latitude */
+  Latitude = Math.atan(SPHI / Math.abs(CPHI));
+  return {
+    x: Longitude,
+    y: Latitude,
+    z: Height
+  };
+}; // cs_geocentric_to_geodetic()
+
+
+/****************************************************************/
+// pj_geocentic_to_wgs84( p )
+//  p = point to transform in geocentric coordinates (x,y,z)
+
+
+/** point object, nothing fancy, just allows values to be
+    passed back and forth by reference rather than by value.
+    Other point classes may be used as long as they have
+    x and y properties, which will get modified in the transform method.
+*/
+exports.geocentricToWgs84 = function(p, datum_type, datum_params) {
+
+  if (datum_type === PJD_3PARAM) {
+    // if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: p.x + datum_params[0],
+      y: p.y + datum_params[1],
+      z: p.z + datum_params[2],
+    };
+  } else if (datum_type === PJD_7PARAM) {
+    var Dx_BF = datum_params[0];
+    var Dy_BF = datum_params[1];
+    var Dz_BF = datum_params[2];
+    var Rx_BF = datum_params[3];
+    var Ry_BF = datum_params[4];
+    var Rz_BF = datum_params[5];
+    var M_BF = datum_params[6];
+    // if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF,
+      y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF,
+      z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF
+    };
+  }
+}; // cs_geocentric_to_wgs84
+
+/****************************************************************/
+// pj_geocentic_from_wgs84()
+//  coordinate system definition,
+//  point to transform in geocentric coordinates (x,y,z)
+exports.geocentricFromWgs84 = function(p, datum_type, datum_params) {
+
+  if (datum_type === PJD_3PARAM) {
+    //if( x[io] === HUGE_VAL )
+    //    continue;
+    return {
+      x: p.x - datum_params[0],
+      y: p.y - datum_params[1],
+      z: p.z - datum_params[2],
+    };
+
+  } else if (datum_type === PJD_7PARAM) {
+    var Dx_BF = datum_params[0];
+    var Dy_BF = datum_params[1];
+    var Dz_BF = datum_params[2];
+    var Rx_BF = datum_params[3];
+    var Ry_BF = datum_params[4];
+    var Rz_BF = datum_params[5];
+    var M_BF = datum_params[6];
+    var x_tmp = (p.x - Dx_BF) / M_BF;
+    var y_tmp = (p.y - Dy_BF) / M_BF;
+    var z_tmp = (p.z - Dz_BF) / M_BF;
+    //if( x[io] === HUGE_VAL )
+    //    continue;
+
+    return {
+      x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp,
+      y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp,
+      z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp
+    };
+  } //cs_geocentric_from_wgs84()
+};
diff --git a/lib/datum_transform.js b/lib/datum_transform.js
index 65173a8..b5a8b3f 100644
--- a/lib/datum_transform.js
+++ b/lib/datum_transform.js
@@ -1,17 +1,13 @@
 var PJD_3PARAM = 1;
 var PJD_7PARAM = 2;
-var PJD_GRIDSHIFT = 3;
 var PJD_NODATUM = 5; // WGS84 or equivalent
-var SRS_WGS84_SEMIMAJOR = 6378137; // only used in grid shift transforms
-var SRS_WGS84_ESQUARED = 0.006694379990141316; //DGR: 2012-07-29
+var datum = require('./datumUtils');
+function checkParams(type) {
+  return (type === PJD_3PARAM || type === PJD_7PARAM);
+}
 module.exports = function(source, dest, point) {
-  var wp, i, l;
-
-  function checkParams(fallback) {
-    return (fallback === PJD_3PARAM || fallback === PJD_7PARAM);
-  }
   // Short cut if the datums are identical.
-  if (source.compare_datums(dest)) {
+  if (datum.compareDatums(source, dest)) {
     return point; // in this case, zero is sucess,
     // whereas cs_compare_datums returns 1 to indicate TRUE
     // confusing, should fix this
@@ -22,78 +18,22 @@ module.exports = function(source, dest, point) {
     return point;
   }
 
-  //DGR: 2012-07-29 : add nadgrids support (begin)
-  var src_a = source.a;
-  var src_es = source.es;
-
-  var dst_a = dest.a;
-  var dst_es = dest.es;
-
-  var fallback = source.datum_type;
   // If this datum requires grid shifts, then apply it to geodetic coordinates.
-  if (fallback === PJD_GRIDSHIFT) {
-    if (this.apply_gridshift(source, 0, point) === 0) {
-      source.a = SRS_WGS84_SEMIMAJOR;
-      source.es = SRS_WGS84_ESQUARED;
-    }
-    else {
-      // try 3 or 7 params transformation or nothing ?
-      if (!source.datum_params) {
-        source.a = src_a;
-        source.es = source.es;
-        return point;
-      }
-      wp = 1;
-      for (i = 0, l = source.datum_params.length; i < l; i++) {
-        wp *= source.datum_params[i];
-      }
-      if (wp === 0) {
-        source.a = src_a;
-        source.es = source.es;
-        return point;
-      }
-      if (source.datum_params.length > 3) {
-        fallback = PJD_7PARAM;
-      }
-      else {
-        fallback = PJD_3PARAM;
-      }
-    }
-  }
-  if (dest.datum_type === PJD_GRIDSHIFT) {
-    dest.a = SRS_WGS84_SEMIMAJOR;
-    dest.es = SRS_WGS84_ESQUARED;
-  }
+
   // Do we need to go through geocentric coordinates?
-  if (source.es !== dest.es || source.a !== dest.a || checkParams(fallback) || checkParams(dest.datum_type)) {
-    //DGR: 2012-07-29 : add nadgrids support (end)
-    // Convert to geocentric coordinates.
-    source.geodetic_to_geocentric(point);
-    // CHECK_RETURN;
-    // Convert between datums
-    if (checkParams(source.datum_type)) {
-      source.geocentric_to_wgs84(point);
-      // CHECK_RETURN;
-    }
-    if (checkParams(dest.datum_type)) {
-      dest.geocentric_from_wgs84(point);
-      // CHECK_RETURN;
-    }
-    // Convert back to geodetic coordinates
-    dest.geocentric_to_geodetic(point);
-    // CHECK_RETURN;
-  }
-  // Apply grid shift to destination if required
-  if (dest.datum_type === PJD_GRIDSHIFT) {
-    this.apply_gridshift(dest, 1, point);
-    // CHECK_RETURN;
+  if (source.es === dest.es && source.a === dest.a && !checkParams(source.datum_type) &&  !checkParams(dest.datum_type)) {
+    return point;
   }
 
-  source.a = src_a;
-  source.es = src_es;
-  dest.a = dst_a;
-  dest.es = dst_es;
+  // Convert to geocentric coordinates.
+  point = datum.geodeticToGeocentric(point, source.es, source.a);
+  // Convert between datums
+  if (checkParams(source.datum_type)) {
+    point = datum.geocentricToWgs84(point, source.datum_type, source.datum_params);
+  }
+  if (checkParams(dest.datum_type)) {
+    point = datum.geocentricFromWgs84(point, dest.datum_type, dest.datum_params);
+  }
+  return datum.geocentricToGeodetic(point, dest.es, dest.a, dest.b);
 
-  return point;
 };
-
diff --git a/lib/deriveConstants.js b/lib/deriveConstants.js
index b8a80a2..38faac0 100644
--- a/lib/deriveConstants.js
+++ b/lib/deriveConstants.js
@@ -1,56 +1,53 @@
-var Datum = require('./constants/Datum');
-var Ellipsoid = require('./constants/Ellipsoid');
-var extend = require('./extend');
-var datum = require('./datum');
-var EPSLN = 1.0e-10;
 // ellipoid pj_set_ell.c
 var SIXTH = 0.1666666666666666667;
 /* 1/6 */
 var RA4 = 0.04722222222222222222;
 /* 17/360 */
 var RA6 = 0.02215608465608465608;
-module.exports = function(json) {
-  // DGR 2011-03-20 : nagrids -> nadgrids
-  if (json.datumCode && json.datumCode !== 'none') {
-    var datumDef = Datum[json.datumCode];
-    if (datumDef) {
-      json.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
-      json.ellps = datumDef.ellipse;
-      json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode;
+var EPSLN = 1.0e-10;
+var Ellipsoid = require('./constants/Ellipsoid');
+
+exports.eccentricity = function(a, b, rf, R_A) {
+  var a2 = a * a; // used in geocentric
+  var b2 = b * b; // used in geocentric
+  var es = (a2 - b2) / a2; // e ^ 2
+  var e = 0;
+  if (R_A) {
+    a *= 1 - es * (SIXTH + es * (RA4 + es * RA6));
+    a2 = a * a;
+    es = 0;
+  } else {
+    e = Math.sqrt(es); // eccentricity
+  }
+  var ep2 = (a2 - b2) / b2; // used in geocentric
+  return {
+    es: es,
+    e: e,
+    ep2: ep2
+  };
+};
+exports.sphere = function (a, b, rf, ellps, sphere) {
+  if (!a) { // do we have an ellipsoid?
+    var ellipse = Ellipsoid[ellps];
+    if (!ellipse) {
+      ellipse = Ellipsoid.WGS84;
     }
-  }
-  if (!json.a) { // do we have an ellipsoid?
-    var ellipse = Ellipsoid[json.ellps] ? Ellipsoid[json.ellps] : Ellipsoid.WGS84;
-    extend(json, ellipse);
-  }
-  if (json.rf && !json.b) {
-    json.b = (1.0 - 1.0 / json.rf) * json.a;
-  }
-  if (json.rf === 0 || Math.abs(json.a - json.b) < EPSLN) {
-    json.sphere = true;
-    json.b = json.a;
-  }
-  json.a2 = json.a * json.a; // used in geocentric
-  json.b2 = json.b * json.b; // used in geocentric
-  json.es = (json.a2 - json.b2) / json.a2; // e ^ 2
-  json.e = Math.sqrt(json.es); // eccentricity
-  if (json.R_A) {
-    json.a *= 1 - json.es * (SIXTH + json.es * (RA4 + json.es * RA6));
-    json.a2 = json.a * json.a;
-    json.b2 = json.b * json.b;
-    json.es = 0;
-  }
-  json.ep2 = (json.a2 - json.b2) / json.b2; // used in geocentric
-  if (!json.k0) {
-    json.k0 = 1.0; //default value
-  }
-  //DGR 2010-11-12: axis
-  if (!json.axis) {
-    json.axis = "enu";
+    a = ellipse.a;
+    b = ellipse.b;
+    rf = ellipse.rf;
   }
 
-  if (!json.datum) {
-    json.datum = datum(json);
-  }
-  return json;
+  if (rf && !b) {
+    b = (1.0 - 1.0 / rf) * a;
+  }
+  if (rf === 0 || Math.abs(a - b) < EPSLN) {
+    sphere = true;
+    b = a;
+  }
+  return {
+    a: a,
+    b: b,
+    rf: rf,
+    sphere: sphere
+  };
 };
diff --git a/lib/index.js b/lib/index.js
index df6c187..a912bc0 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,10 +3,10 @@ proj4.defaultDatum = 'WGS84'; //default datum
 proj4.Proj = require('./Proj');
 proj4.WGS84 = new proj4.Proj('WGS84');
 proj4.Point = require('./Point');
-proj4.toPoint = require("./common/toPoint");
+proj4.toPoint = require('./common/toPoint');
 proj4.defs = require('./defs');
 proj4.transform = require('./transform');
 proj4.mgrs = require('mgrs');
-proj4.version = require('../package.json').version;
+proj4.version = require('./version');
 require('./includedProjections')(proj4);
-module.exports = proj4;
\ No newline at end of file
+module.exports = proj4;
diff --git a/lib/parseCode.js b/lib/parseCode.js
index 88480a3..87386d9 100644
--- a/lib/parseCode.js
+++ b/lib/parseCode.js
@@ -7,11 +7,12 @@ function testObj(code){
 function testDef(code){
   return code in defs;
 }
+var codeWords = ['GEOGCS','GEOCCS','PROJCS','LOCAL_CS'];
+
 function testWKT(code){
-  var codeWords = ['GEOGCS','GEOCCS','PROJCS','LOCAL_CS'];
-  return codeWords.reduce(function(a,b){
-    return a+1+code.indexOf(b);
-  },0);
+  return codeWords.some(function (word) {
+    return code.indexOf(word) > -1;
+  });
 }
 function testProj(code){
   return code[0] === '+';
@@ -22,10 +23,10 @@ function parse(code){
     if (testDef(code)) {
       return defs[code];
     }
-    else if (testWKT(code)) {
+    if (testWKT(code)) {
       return wkt(code);
     }
-    else if (testProj(code)) {
+    if (testProj(code)) {
       return projStr(code);
     }
   }else{
@@ -33,4 +34,4 @@ function parse(code){
   }
 }
 
-module.exports = parse;
\ No newline at end of file
+module.exports = parse;
diff --git a/lib/projString.js b/lib/projString.js
index 53326f2..7affc89 100644
--- a/lib/projString.js
+++ b/lib/projString.js
@@ -4,16 +4,16 @@ var units = require('./constants/units');
 
 module.exports = function(defData) {
   var self = {};
-  var paramObj = {};
-  defData.split("+").map(function(v) {
+  var paramObj = defData.split('+').map(function(v) {
     return v.trim();
   }).filter(function(a) {
     return a;
-  }).forEach(function(a) {
-    var split = a.split("=");
+  }).reduce(function(p, a) {
+    var split = a.split('=');
     split.push(true);
-    paramObj[split[0].toLowerCase()] = split[1];
-  });
+    p[split[0].toLowerCase()] = split[1];
+    return p;
+  }, {});
   var paramName, paramVal, paramOutname;
   var params = {
     proj: 'projName',
diff --git a/lib/projections/tmerc.js b/lib/projections/tmerc.js
index 10fd51e..2eb3617 100644
--- a/lib/projections/tmerc.js
+++ b/lib/projections/tmerc.js
@@ -1,20 +1,24 @@
-var e0fn = require('../common/e0fn');
-var e1fn = require('../common/e1fn');
-var e2fn = require('../common/e2fn');
-var e3fn = require('../common/e3fn');
-var mlfn = require('../common/mlfn');
+// Heavily based on this tmerc projection implementation
+// https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/tmerc.js
+
+var pj_enfn = require('../common/pj_enfn');
+var pj_mlfn = require('../common/pj_mlfn');
+var pj_inv_mlfn = require('../common/pj_inv_mlfn');
 var adjust_lon = require('../common/adjust_lon');
-var HALF_PI = Math.PI/2;
+var HALF_PI = Math.PI / 2;
 var EPSLN = 1.0e-10;
 var sign = require('../common/sign');
-var asinz = require('../common/asinz');
 
 exports.init = function() {
-  this.e0 = e0fn(this.es);
-  this.e1 = e1fn(this.es);
-  this.e2 = e2fn(this.es);
-  this.e3 = e3fn(this.es);
-  this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
+  this.x0 = this.x0 !== undefined ? this.x0 : 0;
+  this.y0 = this.y0 !== undefined ? this.y0 : 0;
+  this.long0 = this.long0 !== undefined ? this.long0 : 0;
+  this.lat0 = this.lat0 !== undefined ? this.lat0 : 0;
+
+  if (this.es) {
+    this.en = pj_enfn(this.es);
+    this.ml0 = pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
+  }
 };
 
 /**
@@ -31,36 +35,65 @@ exports.forward = function(p) {
   var sin_phi = Math.sin(lat);
   var cos_phi = Math.cos(lat);
 
-  if (this.sphere) {
+  if (!this.es) {
     var b = cos_phi * Math.sin(delta_lon);
-    if ((Math.abs(Math.abs(b) - 1)) < 0.0000000001) {
+
+    if ((Math.abs(Math.abs(b) - 1)) < EPSLN) {
       return (93);
     }
     else {
-      x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b));
-      con = Math.acos(cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - b * b));
+      x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b)) + this.x0;
+      y = cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - Math.pow(b, 2));
+      b = Math.abs(y);
+
+      if (b >= 1) {
+        if ((b - 1) > EPSLN) {
+          return (93);
+        }
+        else {
+          y = 0;
+        }
+      }
+      else {
+        y = Math.acos(y);
+      }
+
       if (lat < 0) {
-        con = -con;
+        y = -y;
       }
-      y = this.a * this.k0 * (con - this.lat0);
+
+      y = this.a * this.k0 * (y - this.lat0) + this.y0;
     }
   }
   else {
     var al = cos_phi * delta_lon;
     var als = Math.pow(al, 2);
     var c = this.ep2 * Math.pow(cos_phi, 2);
-    var tq = Math.tan(lat);
+    var cs = Math.pow(c, 2);
+    var tq = Math.abs(cos_phi) > EPSLN ? Math.tan(lat) : 0;
     var t = Math.pow(tq, 2);
+    var ts = Math.pow(t, 2);
     con = 1 - this.es * Math.pow(sin_phi, 2);
-    var n = this.a / Math.sqrt(con);
-    var ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, lat);
+    al = al / Math.sqrt(con);
+    var ml = pj_mlfn(lat, sin_phi, cos_phi, this.en);
 
-    x = this.k0 * n * al * (1 + als / 6 * (1 - t + c + als / 20 * (5 - 18 * t + Math.pow(t, 2) + 72 * c - 58 * this.ep2))) + this.x0;
-    y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24 * (5 - t + 9 * c + 4 * Math.pow(c, 2) + als / 30 * (61 - 58 * t + Math.pow(t, 2) + 600 * c - 330 * this.ep2))))) + this.y0;
+    x = this.a * (this.k0 * al * (1 +
+      als / 6 * (1 - t + c +
+      als / 20 * (5 - 18 * t + ts + 14 * c - 58 * t * c +
+      als / 42 * (61 + 179 * ts - ts * t - 479 * t))))) +
+      this.x0;
 
+    y = this.a * (this.k0 * (ml - this.ml0 +
+      sin_phi * delta_lon * al / 2 * (1 +
+      als / 12 * (5 - t + 9 * c + 4 * cs +
+      als / 30 * (61 + ts - 58 * t + 270 * c - 330 * t * c +
+      als / 56 * (1385 + 543 * ts - ts * t - 3111 * t)))))) +
+      this.y0;
   }
+
   p.x = x;
   p.y = y;
+
   return p;
 };
 
@@ -69,67 +102,66 @@ exports.forward = function(p) {
   */
 exports.inverse = function(p) {
   var con, phi;
-  var delta_phi;
-  var i;
-  var max_iter = 6;
   var lat, lon;
+  var x = (p.x - this.x0) * (1 / this.a);
+  var y = (p.y - this.y0) * (1 / this.a);
 
-  if (this.sphere) {
-    var f = Math.exp(p.x / (this.a * this.k0));
+  if (!this.es) {
+    var f = Math.exp(x / this.k0);
     var g = 0.5 * (f - 1 / f);
-    var temp = this.lat0 + p.y / (this.a * this.k0);
+    var temp = this.lat0 + y / this.k0;
     var h = Math.cos(temp);
-    con = Math.sqrt((1 - h * h) / (1 + g * g));
-    lat = asinz(con);
-    if (temp < 0) {
+    con = Math.sqrt((1 - Math.pow(h, 2)) / (1 + Math.pow(g, 2)));
+    lat = Math.asin(con);
+
+    if (y < 0) {
       lat = -lat;
     }
+
     if ((g === 0) && (h === 0)) {
-      lon = this.long0;
+      lon = 0;
     }
     else {
       lon = adjust_lon(Math.atan2(g, h) + this.long0);
     }
   }
   else { // ellipsoidal form
-    var x = p.x - this.x0;
-    var y = p.y - this.y0;
-
-    con = (this.ml0 + y / this.k0) / this.a;
-    phi = con;
-    for (i = 0; true; i++) {
-      delta_phi = ((con + this.e1 * Math.sin(2 * phi) - this.e2 * Math.sin(4 * phi) + this.e3 * Math.sin(6 * phi)) / this.e0) - phi;
-      phi += delta_phi;
-      if (Math.abs(delta_phi) <= EPSLN) {
-        break;
-      }
-      if (i >= max_iter) {
-        return (95);
-      }
-    } // for()
+    con = this.ml0 + y / this.k0;
+    phi = pj_inv_mlfn(con, this.es, this.en);
+
     if (Math.abs(phi) < HALF_PI) {
       var sin_phi = Math.sin(phi);
       var cos_phi = Math.cos(phi);
-      var tan_phi = Math.tan(phi);
+      var tan_phi = Math.abs(cos_phi) > EPSLN ? Math.tan(phi) : 0;
       var c = this.ep2 * Math.pow(cos_phi, 2);
       var cs = Math.pow(c, 2);
       var t = Math.pow(tan_phi, 2);
       var ts = Math.pow(t, 2);
       con = 1 - this.es * Math.pow(sin_phi, 2);
-      var n = this.a / Math.sqrt(con);
-      var r = n * (1 - this.es) / con;
-      var d = x / (n * this.k0);
+      var d = x * Math.sqrt(con) / this.k0;
       var ds = Math.pow(d, 2);
-      lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24 * (5 + 3 * t + 10 * c - 4 * cs - 9 * this.ep2 - ds / 30 * (61 + 90 * t + 298 * c + 45 * ts - 252 * this.ep2 - 3 * cs)));
-      lon = adjust_lon(this.long0 + (d * (1 - ds / 6 * (1 + 2 * t + c - ds / 20 * (5 - 2 * c + 28 * t - 3 * cs + 8 * this.ep2 + 24 * ts))) / cos_phi));
+      con = con * tan_phi;
+
+      lat = phi - (con * ds / (1 - this.es)) * 0.5 * (1 -
+        ds / 12 * (5 + 3 * t - 9 * c * t + c - 4 * cs -
+        ds / 30 * (61 + 90 * t - 252 * c * t + 45 * ts + 46 * c -
+        ds / 56 * (1385 + 3633 * t + 4095 * ts + 1574 * ts * t))));
+
+      lon = adjust_lon(this.long0 + (d * (1 -
+        ds / 6 * (1 + 2 * t + c -
+        ds / 20 * (5 + 28 * t + 24 * ts + 8 * c * t + 6 * c -
+        ds / 42 * (61 + 662 * t + 1320 * ts + 720 * ts * t)))) / cos_phi));
     }
     else {
       lat = HALF_PI * sign(y);
-      lon = this.long0;
+      lon = 0;
     }
   }
+
   p.x = lon;
   p.y = lat;
+
   return p;
 };
+
 exports.names = ["Transverse_Mercator", "Transverse Mercator", "tmerc"];
diff --git a/lib/projections/utm.js b/lib/projections/utm.js
index 18245cc..5f37300 100644
--- a/lib/projections/utm.js
+++ b/lib/projections/utm.js
@@ -1,12 +1,16 @@
-var D2R = 0.01745329251994329577;
+var adjust_zone = require('../common/adjust_zone');
 var tmerc = require('./tmerc');
+
 exports.dependsOn = 'tmerc';
+
 exports.init = function() {
-  if (!this.zone) {
+  var zone = adjust_zone(this.zone, this.long0);
+  if (!zone) {
     return;
   }
+
   this.lat0 = 0;
-  this.long0 = ((6 * Math.abs(this.zone)) - 183) * D2R;
+  this.long0 = (zone + 0.5) * Math.PI / 30 - Math.PI;
   this.x0 = 500000;
   this.y0 = this.utmSouth ? 10000000 : 0;
   this.k0 = 0.9996;
@@ -15,4 +19,5 @@ exports.init = function() {
   this.forward = tmerc.forward;
   this.inverse = tmerc.inverse;
 };
+
 exports.names = ["Universal Transverse Mercator System", "utm"];
diff --git a/lib/transform.js b/lib/transform.js
index 2ed0fd6..aed86b9 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -6,36 +6,40 @@ var datum_transform = require('./datum_transform');
 var adjust_axis = require('./adjust_axis');
 var proj = require('./Proj');
 var toPoint = require('./common/toPoint');
+function checkNotWGS(source, dest) {
+  return ((source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM) && dest.datumCode !== 'WGS84') || ((dest.datum.datum_type === PJD_3PARAM || dest.datum.datum_type === PJD_7PARAM) && source.datumCode !== 'WGS84');
+}
 module.exports = function transform(source, dest, point) {
   var wgs84;
   if (Array.isArray(point)) {
     point = toPoint(point);
   }
-  function checkNotWGS(source, dest) {
-    return ((source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM) && dest.datumCode !== "WGS84");
-  }
 
   // Workaround for datum shifts towgs84, if either source or destination projection is not wgs84
-  if (source.datum && dest.datum && (checkNotWGS(source, dest) || checkNotWGS(dest, source))) {
+  if (source.datum && dest.datum && checkNotWGS(source, dest)) {
     wgs84 = new proj('WGS84');
-    transform(source, wgs84, point);
+    point = transform(source, wgs84, point);
     source = wgs84;
   }
   // DGR, 2010/11/12
-  if (source.axis !== "enu") {
-    adjust_axis(source, false, point);
+  if (source.axis !== 'enu') {
+    point = adjust_axis(source, false, point);
   }
   // Transform source points to long/lat, if they aren't already.
-  if (source.projName === "longlat") {
-    point.x *= D2R; // convert degrees to radians
-    point.y *= D2R;
+  if (source.projName === 'longlat') {
+    point = {
+      x: point.x * D2R,
+      y: point.y * D2R
+    };
   }
   else {
     if (source.to_meter) {
-      point.x *= source.to_meter;
-      point.y *= source.to_meter;
+      point = {
+        x: point.x * source.to_meter,
+        y: point.y * source.to_meter
+      };
     }
-    source.inverse(point); // Convert Cartesian to longlat
+    point = source.inverse(point); // Convert Cartesian to longlat
   }
   // Adjust for the prime meridian if necessary
   if (source.from_greenwich) {
@@ -47,26 +51,32 @@ module.exports = function transform(source, dest, point) {
 
   // Adjust for the prime meridian if necessary
   if (dest.from_greenwich) {
-    point.x -= dest.from_greenwich;
+    point = {
+      x: point.x - dest.grom_greenwich,
+      y: point.y
+    };
   }
 
-  if (dest.projName === "longlat") {
+  if (dest.projName === 'longlat') {
     // convert radians to decimal degrees
-    point.x *= R2D;
-    point.y *= R2D;
-  }
-  else { // else project
-    dest.forward(point);
+    point = {
+      x: point.x * R2D,
+      y: point.y * R2D
+    };
+  } else { // else project
+    point = dest.forward(point);
     if (dest.to_meter) {
-      point.x /= dest.to_meter;
-      point.y /= dest.to_meter;
+      point = {
+        x: point.x / dest.to_meter,
+        y: point.y / dest.to_meter
+      };
     }
   }
 
   // DGR, 2010/11/12
-  if (dest.axis !== "enu") {
-    adjust_axis(dest, true, point);
+  if (dest.axis !== 'enu') {
+    return adjust_axis(dest, true, point);
   }
 
   return point;
-};
\ No newline at end of file
+};
diff --git a/lib/version-browser.js b/lib/version-browser.js
new file mode 100644
index 0000000..8e946b2
--- /dev/null
+++ b/lib/version-browser.js
@@ -0,0 +1 @@
+module.exports = '2.3.16';
diff --git a/lib/version.js b/lib/version.js
new file mode 100644
index 0000000..81f6e78
--- /dev/null
+++ b/lib/version.js
@@ -0,0 +1 @@
+module.exports = require('../package.json').version;
diff --git a/package.json b/package.json
index b0605f5..ea276ae 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "proj4",
-  "version": "2.3.15-alpha",
+  "version": "2.3.16-alpha",
   "description": "Proj4js is a JavaScript library to transform point coordinates from one coordinate system to another, including datum transformations.",
   "main": "lib/index.js",
   "directories": {
@@ -16,15 +16,6 @@
   },
   "author": "",
   "license": "MIT",
-  "jam": {
-    "main": "dist/proj4.js",
-    "include": [
-      "dist/proj4.js",
-      "README.md",
-      "AUTHORS",
-      "LICENSE.md"
-    ]
-  },
   "devDependencies": {
     "grunt-cli": "~0.1.13",
     "grunt": "~0.4.2",
@@ -40,7 +31,10 @@
     "istanbul": "~0.2.4",
     "tin": "~0.4.0"
   },
+  "browser": {
+    "./lib/version.js": "./lib/version-browser.js"
+  },
   "dependencies": {
     "mgrs": "~0.0.2"
   }
-}
\ No newline at end of file
+}
diff --git a/publish.sh b/publish.sh
index 8d9ef84..93d042f 100755
--- a/publish.sh
+++ b/publish.sh
@@ -5,6 +5,8 @@ VERSION=$(npm ls --json=true proj4js | grep version | awk '{ print $2}'| sed -e
 
 # Build
 git checkout -b build
+echo "module.exports = '$VERSION';" > ./lib/version-browser.js
+git add ./lib/version-browser.js -f
 node_modules/.bin/grunt
 git add dist -f
 git commit -m "build $VERSION"
@@ -15,7 +17,6 @@ git push --tags git at github.com:proj4js/proj4js.git $VERSION
 
 # Publish
 npm publish
-jam publish
 
 # Cleanup
 git checkout master
diff --git a/test/test.js b/test/test.js
index 45bfc32..0d8fa5b 100644
--- a/test/test.js
+++ b/test/test.js
@@ -25,17 +25,18 @@ function startTests(chai, proj4, testPoints) {
       var sweref99tm = '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs';
       var rt90 = '+lon_0=15.808277777799999 +lat_0=0.0 +k=1.0 +x_0=1500000.0 +y_0=0.0 +proj=tmerc +ellps=bessel +units=m +towgs84=414.1,41.3,603.1,-0.855,2.141,-7.023,0 +no_defs';
       var rslt = proj4(sweref99tm, rt90).forward([319180, 6399862]);
-      assert.closeTo(rslt[0], 1271137.927154, 0.000001);
-      assert.closeTo(rslt[1], 6404230.291456, 0.000001);
+      assert.closeTo(rslt[0], 1271137.9275601401, 0.000001);
+      assert.closeTo(rslt[1], 6404230.291448903, 0.000001);
     });
     it('should work with a proj object', function() {
       var sweref99tm = proj4('+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs');
       var rt90 = proj4('+lon_0=15.808277777799999 +lat_0=0.0 +k=1.0 +x_0=1500000.0 +y_0=0.0 +proj=tmerc +ellps=bessel +units=m +towgs84=414.1,41.3,603.1,-0.855,2.141,-7.023,0 +no_defs');
       var rslt = proj4(sweref99tm, rt90).forward([319180, 6399862]);
-      assert.closeTo(rslt[0], 1271137.927154, 0.000001);
-      assert.closeTo(rslt[1], 6404230.291456, 0.000001);
+      assert.closeTo(rslt[0], 1271137.9275601401, 0.000001);
+      assert.closeTo(rslt[1], 6404230.291448903, 0.000001);
     });
   });
+
   describe('proj4', function() {
     describe('core', function() {
       testPoints.forEach(function(testPoint) {
diff --git a/test/testData.js b/test/testData.js
index cb9bd80..b3cd064 100644
--- a/test/testData.js
+++ b/test/testData.js
@@ -147,20 +147,20 @@ var testPoints = [
   {
     code:'PROJCS["Beduaram / TM 13 NE",GEOGCS["Beduaram",DATUM["Beduaram",SPHEROID["Clarke 1880 (IGN)",6378249.2,293.4660212936269,AUTHORITY["EPSG","7011"]],TOWGS84[-106,-87,188,0,0,0,0],AUTHORITY["EPSG","6213"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4213"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",13],PARA [...]
     ll:[5, 25],
-    xy:[-308919.1462828873, 2788738.252386554],
+    xy:[-308919.1234711099, 2788738.255936392],
     acc:{
       ll:5
     }
   },
-   {
+  {
     code:'PROJCS["Beduaram / TM 13 NE",GEOGCS["Beduaram",DATUM["D_Beduaram",SPHEROID["Clarke_1880_IGN",6378249.2,293.4660212936269]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",13],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]',
     ll:[5, 25],
-    xy:[-308919.1462828873, 2788738.252386554],
+    xy:[-308919.1234711099, 2788738.255936392],
     acc:{
       ll:5
     }
-  }
-  ,{
+  },
+  {
     code:'PROJCS["S-JTSK (Ferro) / Krovak",GEOGCS["S-JTSK (Ferro)",DATUM["S_JTSK_Ferro",SPHEROID["Bessel 1841",6377397.155,299.1528128,AUTHORITY["EPSG","7004"]],AUTHORITY["EPSG","6818"]],PRIMEM["Ferro",-17.66666666666667,AUTHORITY["EPSG","8909"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4818"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Krovak"],PARAMETER["latitude_of_center",49.5],PARAMETER["longitude_of_center",42.5],PARAMETER["azimuth",30 [...]
     ll:[17.323583231075897, 49.39440725405376],
     xy:[-544115.474379, -1144058.330762]
@@ -308,6 +308,80 @@ var testPoints = [
     code: 'EPSG:3857',
     ll: [180, 0],
     xy: [20037508.342789, 0]
+  },
+  // these test cases are taken from mapshaper-proj and the test results match
+  {
+    code: '+proj=tmerc +ellps=GRS80 +lat_1=0.5 +lat_2=2 +n=0.5',
+    ll: [2, 1],
+    xy: [222650.79679577847, 110642.2294119271]
+  },
+  {
+    code: '+proj=tmerc +a=6400000 +lat_1=0.5 +lat_2=2 +n=0.5 +datum=none',
+    ll: [2, 1],
+    xy: [223413.46640632232, 111769.14504059685]
+  },
+  {
+    code: '+proj=utm +zone=30 +ellps=GRS80 +lat_1=0.5 +lat_2=2 +n=0.5',
+    ll: [2, 1],
+    xy: [1057002.4052152266, 110955.14117382761]
+  },
+  // these test cases are related to the original issue on GitHub
+  {
+    code: '+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs',
+    ll: [2, 1],
+    xy: [-959006.3439168662, 113457.31706492987],
+    acc: {
+      ll: 5
+    }
+  },
+  {
+    code: '+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs',
+    ll: [31, 70],
+    xy: [1104629.4280255223, 7845845.076400871],
+    acc: {
+      ll: 4
+    }
+  },
+  // these test cases are for Norway snow flake zones
+  {
+    code: '+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs',
+    ll: [59.121778, 1.508527],
+    xy: [8055639.601582392, 297536.7150416747],
+    acc: {
+      ll: 0
+    }
+  },
+  {
+    code: '+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs',
+    ll: [59.121778, 1.508527],
+    xy: [6958363.797581035, 260155.3254079497],
+    acc: {
+      ll: 0
+    }
+  },
+  {
+    code: '+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs',
+    ll: [59.121778, 1.508527],
+    xy: [5980907.454031456, 232674.60895515585],
+    acc: {
+      ll: 1
+    }
+  },
+  {
+    code: '+proj=utm +zone=34 +datum=WGS84 +units=m +no_defs',
+    ll: [79.070672, 20.520579],
+    xy: [7442887.111291251, 3910285.3071145327],
+    acc: {
+      ll: -1.5
+    }
+  },
+  {
+    code: '+proj=utm +zone=35 +datum=WGS84 +units=m +no_defs',
+    ll: [79.070672, 20.520579],
+    xy: [6555309.538050345, 3474309.0216152733],
+    acc: {
+      ll: -0.5
+    }
   }
 ];
 if(typeof module !== 'undefined'){

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/proj4js.git



More information about the Pkg-grass-devel mailing list