Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save alexander-lloyd/6a9daefa34c2631d1ad2b4f6a7b5b305 to your computer and use it in GitHub Desktop.
Save alexander-lloyd/6a9daefa34c2631d1ad2b4f6a7b5b305 to your computer and use it in GitHub Desktop.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
/* - www.movable-type.co.uk/scripts/gridref.js */
/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
/**
* Creates a OsGridRef object
*
* @constructor
* @classdesc Convert OS grid references to/from OSGB latitude/longitude points
* @requires LatLonE, GeoParams (from latlon-ellipse.js)
*
* @param {Number} easting - Easting in metres from OS false origin
* @param {Number} northing - Northing in metres from OS false origin
*/
function OsGridRef(easting, northing) {
this.easting = Math.floor(Number(easting));
this.northing = Math.floor(Number(northing));
}
/**
* Convert (OSGB36) latitude/longitude to Ordnance Survey grid reference easting/northing coordinate
*
* @param {LatLonE} point - OSGB36 latitude/longitude
* @returns {OsGridRef} OS Grid Reference easting/northing
* @throws Error if datum of point is not OSGB36
*/
OsGridRef.latLongToOsGrid = function(point) {
if (point.datum != GeoParams.datum.OSGB36) throw new Error('Can only convert OSGB36 point to OsGrid');
var φ = point.lat.toRadians();
var λ = point.lon.toRadians();
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var φ0 = (49).toRadians(), λ0 = (-2).toRadians(); // NatGrid true origin is 49°N,2°W
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; // n, n², n³
var cosφ = Math.cos(φ), sinφ = Math.sin(φ);
var ν = a*F0/Math.sqrt(1-e2*sinφ*sinφ); // nu = transverse radius of curvature
var ρ = a*F0*(1-e2)/Math.pow(1-e2*sinφ*sinφ, 1.5); // rho = meridional radius of curvature
var η2 = ν/ρ-1; // eta = ?
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (φ-φ0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(φ-φ0) * Math.cos(φ+φ0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(φ-φ0)) * Math.cos(2*(φ+φ0));
var Md = (35/24)*n3 * Math.sin(3*(φ-φ0)) * Math.cos(3*(φ+φ0));
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
var cos3φ = cosφ*cosφ*cosφ;
var cos5φ = cos3φ*cosφ*cosφ;
var tan2φ = Math.tan(φ)*Math.tan(φ);
var tan4φ = tan2φ*tan2φ;
var I = M + N0;
var II = (ν/2)*sinφ*cosφ;
var III = (ν/24)*sinφ*cos3φ*(5-tan2φ+9*η2);
var IIIA = (ν/720)*sinφ*cos5φ*(61-58*tan2φ+tan4φ);
var IV = ν*cosφ;
var V = (ν/6)*cos3φ*(ν/ρ-tan2φ);
var VI = (ν/120) * cos5φ * (5 - 18*tan2φ + tan4φ + 14*η2 - 58*tan2φ*η2);
var Δλ = λ-λ0;
var Δλ2 = Δλ*Δλ, Δλ3 = Δλ2*Δλ, Δλ4 = Δλ3*Δλ, Δλ5 = Δλ4*Δλ, Δλ6 = Δλ5*Δλ;
var N = I + II*Δλ2 + III*Δλ4 + IIIA*Δλ6;
var E = E0 + IV*Δλ + V*Δλ3 + VI*Δλ5;
return new OsGridRef(E, N);
}
/**
* Convert Ordnance Survey grid reference easting/northing coordinate to (OSGB36) latitude/longitude
*
* @param {OsGridRef} gridref - easting/northing to be converted to latitude/longitude
* @returns {LatLonE} latitude/longitude (in OSGB36) of supplied grid reference
*/
OsGridRef.osGridToLatLong = function(gridref) {
var E = gridref.easting;
var N = gridref.northing;
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var φ0 = 49*Math.PI/180, λ0 = -2*Math.PI/180; // NatGrid true origin
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; // n, n², n³
var φ=φ0, M=0;
do {
φ = (N-N0-M)/(a*F0) + φ;
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (φ-φ0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(φ-φ0) * Math.cos(φ+φ0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(φ-φ0)) * Math.cos(2*(φ+φ0));
var Md = (35/24)*n3 * Math.sin(3*(φ-φ0)) * Math.cos(3*(φ+φ0));
M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
} while (N-N0-M >= 0.00001); // ie until < 0.01mm
var cosφ = Math.cos(φ), sinφ = Math.sin(φ);
var ν = a*F0/Math.sqrt(1-e2*sinφ*sinφ); // nu = transverse radius of curvature
var ρ = a*F0*(1-e2)/Math.pow(1-e2*sinφ*sinφ, 1.5); // rho = meridional radius of curvature
var η2 = ν/ρ-1; // eta = ?
var tanφ = Math.tan(φ);
var tan2φ = tanφ*tanφ, tan4φ = tan2φ*tan2φ, tan6φ = tan4φ*tan2φ;
var secφ = 1/cosφ;
var ν3 = ν*ν*ν, ν5 = ν3*ν*ν, ν7 = ν5*ν*ν;
var VII = tanφ/(2*ρ*ν);
var VIII = tanφ/(24*ρ*ν3)*(5+3*tan2φ+η2-9*tan2φ*η2);
var IX = tanφ/(720*ρ*ν5)*(61+90*tan2φ+45*tan4φ);
var X = secφ/ν;
var XI = secφ/(6*ν3)*(ν/ρ+2*tan2φ);
var XII = secφ/(120*ν5)*(5+28*tan2φ+24*tan4φ);
var XIIA = secφ/(5040*ν7)*(61+662*tan2φ+1320*tan4φ+720*tan6φ);
var dE = (E-E0), dE2 = dE*dE, dE3 = dE2*dE, dE4 = dE2*dE2, dE5 = dE3*dE2, dE6 = dE4*dE2, dE7 = dE5*dE2;
φ = φ - VII*dE2 + VIII*dE4 - IX*dE6;
var λ = λ0 + X*dE - XI*dE3 + XII*dE5 - XIIA*dE7;
return new LatLonE(φ.toDegrees(), λ.toDegrees(), GeoParams.datum.OSGB36);
}
/**
* Converts standard grid reference (eg 'SU387148') to fully numeric ref (eg [438700,114800])
*
* @param {String} gridref - standard format OS grid reference
* @returns {OsGridRef} numeric version of grid reference in metres from false origin, centred on
* supplied grid square
*/
OsGridRef.parse = function(gridref) {
gridref = gridref.trim();
// get numeric values of letter references, mapping A->0, B->1, C->2, etc:
var l1 = gridref.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0);
var l2 = gridref.toUpperCase().charCodeAt(1) - 'A'.charCodeAt(0);
// shuffle down letters after 'I' since 'I' is not used in grid:
if (l1 > 7) l1--;
if (l2 > 7) l2--;
// convert grid letters into 100km-square indexes from false origin (grid square SV):
var e = ((l1-2)%5)*5 + (l2%5);
var n = (19-Math.floor(l1/5)*5) - Math.floor(l2/5);
if (e<0 || e>6 || n<0 || n>12) return new OsGridRef(NaN, NaN);
// skip grid letters to get numeric part of ref, stripping any spaces:
gridref = gridref.slice(2).replace(/ /g,'');
// append numeric part of references to grid index:
e += gridref.slice(0, gridref.length/2);
n += gridref.slice(gridref.length/2);
// normalise to 1m grid, rounding up to centre of grid square:
switch (gridref.length) {
case 0: e += '50000'; n += '50000'; break;
case 2: e += '5000'; n += '5000'; break;
case 4: e += '500'; n += '500'; break;
case 6: e += '50'; n += '50'; break;
case 8: e += '5'; n += '5'; break;
case 10: break; // 10-digit refs are already 1m
default: return new OsGridRef(NaN, NaN);
}
return new OsGridRef(e, n);
}
/**
* Converts ‘this’ numeric grid reference to standard OS grid reference
*
* @param {Number} [digits=6] Precision of returned grid reference (6 digits = metres)
* @returns {String} this grid reference in standard format
*/
OsGridRef.prototype.toString = function(digits) {
digits = (typeof digits == 'undefined') ? 10 : digits;
var e = this.easting;
var n = this.northing;
if (e==NaN || n==NaN) return '??';
// get the 100km-grid indices
var e100k = Math.floor(e/100000), n100k = Math.floor(n/100000);
if (e100k<0 || e100k>6 || n100k<0 || n100k>12) return '';
// translate those into numeric equivalents of the grid letters
var l1 = (19-n100k) - (19-n100k)%5 + Math.floor((e100k+10)/5);
var l2 = (19-n100k)*5%25 + e100k%5;
// compensate for skipped 'I' and build grid letter-pairs
if (l1 > 7) l1++;
if (l2 > 7) l2++;
var letPair = String.fromCharCode(l1+'A'.charCodeAt(0), l2+'A'.charCodeAt(0));
// strip 100km-grid indices from easting & northing, and reduce precision
e = Math.floor((e%100000)/Math.pow(10,5-digits/2));
n = Math.floor((n%100000)/Math.pow(10,5-digits/2));
var gridRef = letPair + ' ' + e.padLz(digits/2) + ' ' + n.padLz(digits/2);
return gridRef;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Trims whitespace from string (q.v. blog.stevenlevithan.com/archives/faster-trim-javascript) */
if (typeof String.prototype.trim == 'undefined') {
String.prototype.trim = function() {
return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
}
/** Pads a number with sufficient leading zeros to make it w chars wide */
if (typeof String.prototype.padLz == 'undefined') {
Number.prototype.padLz = function(w) {
var n = this.toString();
var l = n.length;
for (var i=0; i<w-l; i++) n = '0' + n;
return n;
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Geodesy tools for an ellipsoidal earth model (c) Chris Veness 2005-2014 */
/* */
/* Includes methods for converting lat/lon coordinates bewteen different coordinate systems. */
/* - www.movable-type.co.uk/scripts/latlong-convert-coords.html */
/* */
/* Usage: to eg convert WGS84 coordinate to OSGB coordinate: */
/* - var wgs84 = new LatLonE(latWGS84, lonWGS84, GeoParams.datum.WGS84); */
/* - var osgb = wgs84.convertDatum(GeoParams.datum.OSGB36); */
/* - var latOSGB = osgb.lat, lonOSGB = osgb.lon; */
/* */
/* q.v. Ordnance Survey 'A guide to coordinate systems in Great Britain' Section 6 */
/* - www.ordnancesurvey.co.uk/docs/support/guide-coordinate-systems-great-britain.pdf */
/* */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
/**
* Ellipsoid parameters and datum parameters for transforming lat/lon coordinates between different
* coordinate systems.
*
* @namespace
*/
var GeoParams = {};
/**
* Ellipsoid parameters; major axis (a), minor axis (b), and flattening (f) for each ellipsoid.
*/
GeoParams.ellipsoid = {
WGS84: { a: 6378137, b: 6356752.3142, f: 1/298.257223563 },
GRS80: { a: 6378137, b: 6356752.314140, f: 1/298.257222101 },
Airy1830: { a: 6377563.396, b: 6356256.909, f: 1/299.3249646 },
AiryModified: { a: 6377340.189, b: 6356034.448, f: 1/299.32496 },
Intl1924: { a: 6378388.000, b: 6356911.946, f: 1/297.0 },
Bessel1841: { a: 6377397.155, b: 6356078.963, f: 1/299.152815351 }
};
/**
* Datums; with associated *ellipsoid* and Helmert *transform* parameters to convert from WGS84
* into given datum.
*/
GeoParams.datum = {
WGS84: {
ellipsoid: GeoParams.ellipsoid.WGS84,
transform: { tx: 0.0, ty: 0.0, tz: 0.0, // m
rx: 0.0, ry: 0.0, rz: 0.0, // sec
s: 0.0 } // ppm
},
OSGB36: { // www.ordnancesurvey.co.uk/docs/support/guide-coordinate-systems-great-britain.pdf
ellipsoid: GeoParams.ellipsoid.Airy1830,
transform: { tx: -446.448, ty: 125.157, tz: -542.060, // m
rx: -0.1502, ry: -0.2470, rz: -0.8421, // sec
s: 20.4894 } // ppm
},
ED50: { // og.decc.gov.uk/en/olgs/cms/pons_and_cop/pons/pon4/pon4.aspx
ellipsoid: GeoParams.ellipsoid.Intl1924,
transform: { tx: 89.5, ty: 93.8, tz: 123.1, // m
rx: 0.0, ry: 0.0, rz: 0.156, // sec
s: -1.2 } // ppm
},
Irl1975: { // maps.osni.gov.uk/CMS_UserFiles/file/The_irish_grid.pdf
ellipsoid: GeoParams.ellipsoid.AiryModified,
transform: { tx: -482.530, ty: 130.596, tz: -564.557, // m
rx: -1.042, ry: -0.214, rz: -0.631, // sec
s: -8.150 } // ppm
},
TokyoJapan: { // www.geocachingtoolbox.com?page=datumEllipsoidDetails
ellipsoid: GeoParams.ellipsoid.Bessel1841,
transform: { tx: 148, ty: -507, tz: -685, // m
rx: 0, ry: 0, rz: 0, // sec
s: 0 } // ppm
}
};
/**
* Creates lat/lon (polar) point with latitude & longitude values and height above ellipsoid, on a
* specified datum.
*
* @classdesc Library of geodesy functions for operations on an ellipsoidal earth model.
* @requires GeoParams
* @requires Vector3d
*
* @constructor
* @param {number} lat - Geodetic latitude in degrees.
* @param {number} lon - Longitude in degrees.
* @param {GeoParams.datum} [datum=WGS84] - Datum this point is defined within.
* @param {number} [height=0] - Height above ellipsoid, in metres.
*/
function LatLonE(lat, lon, datum, height) {
if (typeof datum == 'undefined') datum = GeoParams.datum.WGS84;
if (typeof height == 'undefined') height = 0;
this.lat = Number(lat);
this.lon = Number(lon);
this.datum = datum;
this.height = Number(height);
}
/**
* Converts ‘this’ lat/lon coordinate to new coordinate system.
*
* @param {GeoParams.datum} toDatum - Datum this coordinate is to be converted to.
* @returns {LatLonE} This point converted to new datum.
*/
LatLonE.prototype.convertDatum = function(toDatum) {
var oldLatLon = this;
if (oldLatLon.datum == GeoParams.datum.WGS84) {
// converting from WGS84
var transform = toDatum.transform;
}
if (toDatum == GeoParams.datum.WGS84) {
// converting to WGS84; use inverse transform (don't overwrite original!)
var transform = {};
for (var param in oldLatLon.datum.transform) {
transform[param] = -oldLatLon.datum.transform[param];
}
}
if (typeof transform == 'undefined') {
// neither this.datum nor toDatum are WGS84: convert this to WGS84 first
oldLatLon = this.convertDatum(GeoParams.datum.WGS84);
var transform = toDatum.transform;
}
// convert polar to cartesian
var cartesian = oldLatLon.toCartesian();
// apply transform
cartesian = cartesian.applyTransform(transform);
// convert cartesian to polar
var newLatLon = cartesian.toLatLon(toDatum);
return newLatLon;
}
/**
* Converts ‘this’ point from polar (lat/lon) coordinates to cartesian (x/y/z) coordinates.
*
* @returns {Vector3d} Vector pointing to lat/lon point, with x, y, z in metres from earth centre.
*/
LatLonE.prototype.toCartesian = function() {
var φ = this.lat.toRadians(), λ = this.lon.toRadians(), H = this.height;
var a = this.datum.ellipsoid.a, b = this.datum.ellipsoid.b;
var sinφ = Math.sin(φ), cosφ = Math.cos(φ);
var sinλ = Math.sin(λ), cosλ = Math.cos(λ);
var eSq = (a*a - b*b) / (a*a);
var ν = a / Math.sqrt(1 - eSq*sinφ*sinφ);
var x = (ν+H) * cosφ * cosλ;
var y = (ν+H) * cosφ * sinλ;
var z = ((1-eSq)*ν + H) * sinφ;
var point = new Vector3d(x, y, z);
return point;
}
/**
* Converts ‘this’ point from cartesian (x/y/z) coordinates to polar (lat/lon) coordinates on
* specified datum.
*
* @augments Vector3d
* @param {GeoParams.datum.transform} datum - Datum to use when converting point.
*/
Vector3d.prototype.toLatLon = function(datum) {
var x = this.x, y = this.y, z = this.z;
var a = datum.ellipsoid.a, b = datum.ellipsoid.b;
var eSq = (a*a - b*b) / (a*a);
var p = Math.sqrt(x*x + y*y);
var φ = Math.atan2(z, p*(1-eSq)), φʹ; // initial value of φ
var precision = 1 / a; // 1m: Helmert transform cannot generally do better than a few metres
do {
var sinφ = Math.sin(φ);
var ν = a / Math.sqrt(1 - eSq*sinφ*sinφ);
φʹ = φ;
φ = Math.atan2(z + eSq*ν*sinφ, p);
} while (Math.abs(φ-φʹ) > precision);
var λ = Math.atan2(y, x);
var H = p/Math.cos(φ) - ν;
var point = new LatLonE(φ.toDegrees(), λ.toDegrees(), datum, H);
return point;
}
/**
* Applies Helmert transform to ‘this’ point using transform parameters t.
*
* @private
* @augments Vector3d
* @param {GeoParams.datum.transform} t - Transform to apply to this point.
*/
Vector3d.prototype.applyTransform = function(t) {
var x1 = this.x, y1 = this.y, z1 = this.z;
var tx = t.tx, ty = t.ty, tz = t.tz;
var rx = (t.rx/3600).toRadians(); // normalise seconds to radians
var ry = (t.ry/3600).toRadians(); // normalise seconds to radians
var rz = (t.rz/3600).toRadians(); // normalise seconds to radians
var s1 = t.s/1e6 + 1; // normalise ppm to (s+1)
// apply transform
var x2 = tx + x1*s1 - y1*rz + z1*ry;
var y2 = ty + x1*rz + y1*s1 - z1*rx;
var z2 = tz - x1*ry + y1*rx + z1*s1;
var point = new Vector3d(x2, y2, z2);
return point;
}
/**
* Returns a string representation of ‘this’ point, formatted as degrees, degrees+minutes, or
* degrees+minutes+seconds.
*
* @param {string} [format=dms] - Format point as 'd', 'dm', 'dms'.
* @param {number} [dp=0|2|4] - Number of decimal places to use - default 0 for dms, 2 for dm, 4 for d.
* @returns {string} Comma-separated latitude/longitude.
*/
LatLonE.prototype.toString = function(format, dp) {
return Geo.toLat(this.lat, format, dp) + ', ' + Geo.toLon(this.lon, format, dp);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Extend Number object with method to convert numeric degrees to radians */
if (typeof Number.prototype.toRadians == 'undefined') {
Number.prototype.toRadians = function() { return this * Math.PI / 180; }
}
/** Extend Number object with method to convert radians to numeric (signed) degrees */
if (typeof Number.prototype.toDegrees == 'undefined') {
Number.prototype.toDegrees = function() { return this * 180 / Math.PI; }
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vector handling functions (c) Chris Veness 2011-2014 */
/* */
/* These are generic 3-d vector manipulation routines. */
/* */
/* In a geodesy context, these may be used to represent: */
/* - n-vector representing a normal to point on Earth's surface */
/* - earth-centered, earth fixed vector (= n-vector for spherical model) */
/* - great circle normal to vector */
/* - motion vector on Earth's surface */
/* - etc */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
'use strict';
/**
* Creates a 3-d vector.
*
* The vector may be normalised, or use x/y/z values for eg height relative to the sphere or
* ellipsoid, distance from earth centre, etc.
*
* @classdesc Tools for manipulating 3-d vectors, to support various latitude/longitude functions.
*
* @constructor
* @param {number} x - X component of vector.
* @param {number} y - Y component of vector.
* @param {number} z - Z component of vector.
*/
function Vector3d(x, y, z) {
this.x = Number(x);
this.y = Number(y);
this.z = Number(z);
}
/**
* Adds supplied vector to ‘this’ vector.
*
* @param {Vector3d} v - Vector to be added to this vector.
* @returns {Vector3d} Vector representing sum of this and v.
*/
Vector3d.prototype.plus = function(v) {
return new Vector3d(this.x + v.x, this.y + v.y, this.z + v.z);
}
/**
* Subtracts supplied vector from ‘this’ vector.
*
* @param {Vector3d} v - Vector to be subtracted from this vector.
* @returns {Vector3d} Vector representing difference between this and v.
*/
Vector3d.prototype.minus = function(v) {
return new Vector3d(this.x - v.x, this.y - v.y, this.z - v.z);
}
/**
* Multiplies ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to multiply this vector by.
* @returns {Vector3d} Vector scaled by x.
*/
Vector3d.prototype.times = function(x) {
x = Number(x);
//console.log(this.toString(), x, (new Vector3d(this.x * x, this.y * x, this.z * x)).toString());
return new Vector3d(this.x * x, this.y * x, this.z * x);
}
/**
* Divides ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to divide this vector by.
* @returns {Vector3d} Vector divided by x.
*/
Vector3d.prototype.dividedBy = function(x) {
x = Number(x);
return new Vector3d(this.x / x, this.y / x, this.z / x);
}
/**
* Multiplies ‘this’ vector by the supplied vector using dot (scalar) product.
*
* @param {Vector3d} v - Vector to be dotted with this vector.
* @returns {number} Dot product of ‘this’ and v.
*/
Vector3d.prototype.dot = function(v) {
return this.x*v.x + this.y*v.y + this.z*v.z;
}
/**
* Multiplies ‘this’ vector by the supplied vector using cross (vector) product.
*
* @param {Vector3d} v - Vector to be crossed with this vector.
* @returns {Vector3d} Cross product of ‘this’ and v.
*/
Vector3d.prototype.cross = function(v) {
var x = this.y*v.z - this.z*v.y;
var y = this.z*v.x - this.x*v.z;
var z = this.x*v.y - this.y*v.x;
return new Vector3d(x, y, z);
}
/**
* Negates a vector to point in the opposite direction
*
* @returns {Vector3d} Negated vector.
*/
Vector3d.prototype.negate = function() {
return new Vector3d(-this.x, -this.y, -this.z);
}
/**
* Length (magnitude or norm) of ‘this’ vector
*
* @returns {number} Magnitude of this vector.
*/
Vector3d.prototype.length = function() {
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
}
/**
* Normalizes a vector to its unit vector
* – if the vector is already unit or is zero magnitude, this is a no-op.
*
* @returns {Vector3d} Normalised version of this vector.
*/
Vector3d.prototype.unit = function() {
var norm = this.length();
if (norm == 1) return this;
if (norm == 0) return this;
var x = this.x/norm;
var y = this.y/norm;
var z = this.z/norm;
return new Vector3d(x, y, z);
}
/**
* Calculates the angle between ‘this’ vector and supplied vector.
*
* @param {Vector3d} v
* @returns {number} Angle (in signed radians) between this vector and supplied vector.
*/
Vector3d.prototype.angleTo = function(v) {
var sinθ = this.cross(v).length();
var cosθ = this.dot(v);
return Math.atan2(sinθ, cosθ);
}
/**
* Rotates ‘this’ point around an axis by a specified angle.
*
* @param {Vector3d} axis - The axis being rotated around.
* @param {number} theta - The angle of rotation (in radians).
* @returns {Vector3d} The rotated point.
*/
Vector3d.prototype.rotateAround = function(axis, theta) {
// en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
// en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
var p1 = this.unit();
var p = [ p1.x, p1.y, p1.z ]; // the point being rotated
var a = axis.unit(); // the axis being rotated around
var s = Math.sin(theta);
var c = Math.cos(theta);
// quaternion-derived rotation matrix
var q = [ [ a.x*a.x*(1-c) + c, a.x*a.y*(1-c) - a.z*s, a.x*a.z*(1-c) + a.y*s],
[ a.y*a.x*(1-c) + a.z*s, a.y*a.y*(1-c) + c, a.y*a.z*(1-c) - a.x*s],
[ a.z*a.x*(1-c) - a.y*s, a.z*a.y*(1-c) + a.x*s, a.z*a.z*(1-c) + c ] ];
// multiply q × p
var qp = [0, 0, 0];
for (var i=0; i<3; i++) {
for (var j=0; j<3; j++) {
qp[i] += q[i][j] * p[j];
}
}
var p2 = new Vector3d(qp[0], qp[1], qp[2]);
return p2;
// qv en.wikipedia.org/wiki/Rodrigues'_rotation_formula...
}
/**
* String representation of vector.
*
* @param {number} [precision=3] - Number of decimal places to be used.
* @returns {string} Vector represented as [x,y,z].
*/
Vector3d.prototype.toString = function(precision) {
if (typeof precision == 'undefined') precision = 3;
var p = Number(precision);
var str = '[' + this.x.toFixed(p) + ',' + this.y.toFixed(p) + ',' + this.z.toFixed(p) + ']';
return str;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var fs = require('fs');
var csv = require('csv');
var outputObj = {
type: "FeatureCollection",
features: []
};
console.log("{\"type\": \"FeatureCollection\", \"features\": [");
var lines = fs.readFileSync(process.argv[2]).toString();
csv.parse(lines, {comment: '#', escape: '\\'}, function(err, output){
console.error(err);
output.forEach(function (line) {
if(line[32]){
var parsedOSGridRef = OsGridRef.parse(line[32]);
var latLong = OsGridRef.osGridToLatLong(parsedOSGridRef);
var geoJSONsegment = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [latLong.lon, latLong.lat]
},
properties: {
name: line[0],
type: line[3]
}
}
outputObj.features.push(geoJSONsegment);
console.log(JSON.stringify(geoJSONsegment));
console.log(',');
}
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment