mproj
Version:
A JavaScript port of the Proj.4 cartographic projections library
1,862 lines (1,654 loc) • 358 kB
JavaScript
(function(){
// add math.h functions to library scope
// (to make porting projection functions simpler)
var fabs = Math.abs,
floor = Math.floor,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
acos = Math.acos,
atan = Math.atan,
atan2 = Math.atan2,
sqrt = Math.sqrt,
pow = Math.pow,
exp = Math.exp,
log = Math.log,
hypot = Math.hypot,
sinh = Math.sinh,
cosh = Math.cosh,
MIN = Math.min,
MAX = Math.max;
// constants from math.h
var HUGE_VAL = Infinity,
M_PI = Math.PI;
// from proj_api.h
var RAD_TO_DEG = 57.295779513082321,
DEG_TO_RAD = 0.017453292519943296;
// from pj_transform.c
var SRS_WGS84_SEMIMAJOR = 6378137;
var SRS_WGS84_ESQUARED = 0.0066943799901413165;
// math constants from project.h
var M_FORTPI = M_PI / 4,
M_HALFPI = M_PI / 2,
M_PI_HALFPI = 1.5 * M_PI,
M_TWOPI = 2 * M_PI,
M_TWO_D_PI = 2 / M_PI,
M_TWOPI_HALFPI = 2.5 * M_PI;
// datum types
var PJD_UNKNOWN = 0,
PJD_3PARAM = 1,
PJD_7PARAM = 2,
PJD_GRIDSHIFT = 3,
PJD_WGS84 = 4;
// named errors
var PJD_ERR_GEOCENTRIC = -45,
PJD_ERR_AXIS = -47,
PJD_ERR_GRID_AREA = -48,
PJD_ERR_CATALOG = -49;
// common
var EPS10 = 1e-10;
var PJ_LOG_NONE = 0,
PJ_LOG_ERROR = 1,
PJ_LOG_DEBUG_MAJOR = 2,
PJ_LOG_DEBUG_MINOR = 3;
// context of currently running projection function
// (Unlike Proj.4, we use a single ctx object)
var ctx = {
last_errno: 0,
debug_level: PJ_LOG_NONE,
logger: null // TODO: implement
};
var pj_err_list = [
"no arguments in initialization list", /* -1 */
"no options found in 'init' file", /* -2 */
"invalid init= string", /* -3 */ // Proj.4 text: "no colon in init= string",
"projection not named", /* -4 */
"unknown projection id", /* -5 */
"effective eccentricity = 1", /* -6 */
"unknown unit conversion id", /* -7 */
"invalid boolean param argument", /* -8 */
"unknown elliptical parameter name", /* -9 */
"reciprocal flattening (1/f) = 0", /* -10 */
"|radius reference latitude| > 90", /* -11 */
"squared eccentricity < 0", /* -12 */
"major axis or radius = 0 or not given", /* -13 */
"latitude or longitude exceeded limits", /* -14 */
"invalid x or y", /* -15 */
"improperly formed DMS value", /* -16 */
"non-convergent inverse meridional dist", /* -17 */
"non-convergent inverse phi2", /* -18 */
"acos/asin: |arg| >1+1e-14", /* -19 */
"tolerance condition error", /* -20 */
"conic lat_1 = -lat_2", /* -21 */
"lat_1 >= 90", /* -22 */
"lat_1 = 0", /* -23 */
"lat_ts >= 90", /* -24 */
"no distance between control points", /* -25 */
"projection not selected to be rotated", /* -26 */
"W <= 0 or M <= 0", /* -27 */
"lsat not in 1-5 range", /* -28 */
"path not in range", /* -29 */
"h <= 0", /* -30 */
"k <= 0", /* -31 */
"lat_0 = 0 or 90 or alpha = 90", /* -32 */
"lat_1=lat_2 or lat_1=0 or lat_2=90", /* -33 */
"elliptical usage required", /* -34 */
"invalid UTM zone number", /* -35 */
"arg(s) out of range for Tcheby eval", /* -36 */
"failed to find projection to be rotated", /* -37 */
"failed to load datum shift file", /* -38 */
"both n & m must be spec'd and > 0", /* -39 */
"n <= 0, n > 1 or not specified", /* -40 */
"lat_1 or lat_2 not specified", /* -41 */
"|lat_1| == |lat_2|", /* -42 */
"lat_0 is pi/2 from mean lat", /* -43 */
"unparseable coordinate system definition", /* -44 */
"geocentric transformation missing z or ellps", /* -45 */
"unknown prime meridian conversion id", /* -46 */
"illegal axis orientation combination", /* -47 */
"point not within available datum shift grids", /* -48 */
"invalid sweep axis, choose x or y",
"invalid value for h", // -50
"point outside of projection domain" // -51 taken from Proj v9
];
// see pj_transform.c CHECK_RETURN()
function check_fatal_error() {
var code = ctx.last_errno;
if (!code) return;
if (code > 0 || !is_transient_error(code)) {
e_error(code);
} else {
// transient error
// TODO: consider a strict mode that throws an error
}
}
function is_transient_error(code) {
return transient_error.indexOf(code) > -1;
}
var transient_error = [-14, -15, -17, -18, -19, -20, -27, -48];
function pj_ctx_set_errno(code) {
ctx.last_errno = code;
}
function f_error() {
pj_ctx_set_errno(-20);
}
function i_error() {
pj_ctx_set_errno(-20);
}
function error_msg(code) {
return pj_err_list[~code] || "unknown error";
}
// alias for e_error()
function error(code) {
e_error(code);
}
// a fatal error
// see projects.h E_ERROR macro
function e_error(code) {
pj_ctx_set_errno(code);
fatal();
}
function fatal(msg, o) {
if (!o) o = {};
if (!o.code) o.code = ctx.last_errno || 0;
if (!msg) msg = error_msg(o.code);
// reset error code, so processing can continue after this error is handled
ctx.last_errno = 0;
throw new ProjError(msg, o);
}
function ProjError(msg, o) {
var err = new Error(msg);
err.name = 'ProjError';
Object.keys(o).forEach(function(k) {
err[k] = o[k];
});
return err;
}
function dmstor(str) {
return dmstod(str) * DEG_TO_RAD;
}
// Parse a formatted value in DMS DM or D to a numeric value
// Delimiters: D|d (degrees), ' (minutes), " (seconds)
function dmstod(str) {
var match = /(-?[0-9.]+)d?([0-9.]*)'?([0-9.]*)"?([nsew]?)$/i.exec(str);
var d = NaN;
var deg, min, sec;
if (match) {
deg = match[1] || '0';
min = match[2] || '0';
sec = match[3] || '0';
d = (+deg) + (+min) / 60 + (+sec) / 3600;
if (/[ws]/i.test(match[4])) {
d = -d;
}
}
if (isNaN(d)) {
// throw an exception instead of just setting an error code
// (assumes this function is called by pj_init() or a cli program,
// where an exception is more appropriate)
e_error(-16);
// pj_ctx_set_errno(-16);
// d = HUGE_VAL;
}
return d;
}
function pj_atof(str) {
return pj_strtod(str);
}
function pj_strtod(str) {
return parseFloat(str);
}
/* types
t test for presence
i integer
d simple real
r dms or decimal degrees
s string
b boolean
*/
// see pj_param.c
// this implementation is slightly different
function pj_param(params, code) {
var type = code[0],
name = code.substr(1),
obj = params[name],
isset = obj !== void 0,
val, param;
if (type == 't') {
val = isset;
} else if (isset) {
param = obj.param;
obj.used = true;
if (type == 'i') {
val = parseInt(param);
} else if (type == 'd') {
// Proj.4 handles locale-specific decimal mark
// TODO: what to do about NaNs
val = pj_atof(param);
} else if (type == 'r') {
val = dmstor(param);
} else if (type == 's') {
val = String(param);
} else if (type == 'b') {
if (param == 'T' || param == 't' || param === true) {
val = true;
} else if (param == 'F' || param == 'f') {
val = false;
} else {
pj_ctx_set_errno(-8);
val = false;
}
}
} else {
// value is not set; use default
val = {
i: 0,
b: false,
d: 0,
r: 0,
s: ''
}[type];
}
if (val === void 0) {
fatal("invalid request to pj_param, fatal");
}
return val;
}
// convert arguments in a proj4 definition string into object properties
// (not in Proj.4)
function pj_get_params(args) {
var rxp = /\+([a-z][a-z0-9_]*(?:=[^\s]*)?)/gi;
var params = {};
var match;
while (match = rxp.exec(args)) {
pj_mkparam(params, match[1]);
}
return params;
}
// different from Proj.4
function pj_mkparam(params, token) {
var parts = token.split('=');
var name, val;
if (parts.length == 1) {
name = token;
val = true;
} else {
name = parts[0];
val = token.substr(parts[0].length + 1);
}
params[name] = {used: false, param: val};
}
var pj_list = {};
function pj_add(func, key, name, desc) {
pj_list[key] = {
init: func,
name: name,
description: desc
};
}
/* @pj_param */
function pj_is_latlong(P) {
return !P || P.is_latlong;
}
function pj_is_geocent(P) {
return !P || P.is_geocent;
}
function get_geod_defn(P) {
var got_datum = false,
defn = '';
if ('datum' in P.params) {
got_datum = true;
defn += get_param(P, 'datum');
} else if ('R' in P.params) {
// moving R above other params, to match sequence in pj_ell_set.js
defn += get_param(P, 'R');
} else if ('ellps' in P.params) {
defn += get_param(P, 'ellps');
} else if ('a' in P.params) {
defn += get_param(P, 'a');
if ('b' in P.params) {
defn += get_param(P, 'b');
} else if ('es' in P.params) {
defn += get_param(P, 'es');
} else if ('f' in P.params) {
defn += get_param(P, 'f');
} else {
defn += ' +es=' + P.es;
}
} else {
error(-13);
}
if (!got_datum) {
defn += get_param(P, 'towgs84');
defn += get_param(P, 'nadgrids');
}
// defn += get_param(P, 'R'); // moved to above ellps
defn += get_param(P, 'R_A');
defn += get_param(P, 'R_V');
defn += get_param(P, 'R_a');
defn += get_param(P, 'R_lat_a');
defn += get_param(P, 'R_lat_g');
defn += get_param(P, 'pm');
return defn;
}
// Convert an initialized proj object back to a Proj.4 string
function get_proj_defn(P) {
// skip geodetic params and some initialization-related params
var skip = 'datum,ellps,a,b,es,rf,f,towgs84,nadgrids,R,R_A,R_V,R_a,R_lat_a,R_lat_g,pm,init,no_defs'.split(',');
var defn = '';
Object.keys(P.params).forEach(function(name) {
if (skip.indexOf(name) == -1) {
defn += get_param(P, name);
}
});
// add geodetic params
defn += get_geod_defn(P);
return defn.trim();
}
function get_param(P, name) {
var param = '';
if (name in P.params) {
param = ' +' + name;
if (P.params[name].param !== true) {
param += '=' + pj_param(P.params, 's' + name);
}
}
return param;
}
var pj_datums = [
/* id defn ellipse_id comments */
["WGS84", "towgs84=0,0,0", "WGS84", "WGS_1984"], // added comment for wkt creation
["GGRS87", "towgs84=-199.87,74.79,246.62", "GRS80", "Greek_Geodetic_Reference_System_1987"],
["NAD83", "towgs84=0,0,0", "GRS80", "North_American_Datum_1983"],
// nadgrids not supported; NAD27 will trigger an error
["NAD27", "nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", "clrk66", "North_American_Datum_1927"],
["potsdam", "towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7", "bessel", "Potsdam Rauenberg 1950 DHDN"],
["carthage","towgs84=-263.0,6.0,431.0", "clrk80ign", "Carthage 1934 Tunisia"],
["hermannskogel", "towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232", "bessel", "Hermannskogel"],
["ire65", "towgs84=482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", "mod_airy", "Ireland 1965"],
["nzgd49", "towgs84=59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", "intl", "New Zealand Geodetic Datum 1949"],
["OSGB36", "towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", "airy", "OSGB 1936"],
[null, null, null, null]
];
var pj_prime_meridians = [
// id definition
["greenwich", "0dE"],
["lisbon", "9d07'54.862\"W"],
["paris", "2d20'14.025\"E"],
["bogota", "74d04'51.3\"W"],
["madrid", "3d41'16.58\"W"],
["rome", "12d27'8.4\"E"],
["bern", "7d26'22.5\"E"],
["jakarta", "106d48'27.79\"E"],
["ferro", "17d40'W"],
["brussels", "4d22'4.71\"E"],
["stockholm", "18d3'29.8\"E"],
["athens", "23d42'58.815\"E"],
["oslo", "10d43'22.5\"E"],
[null, null]
];
function find_prime_meridian(id) {
var defn = pj_prime_meridians.reduce(function(memo, arr) {
return arr[0] === id ? arr : memo;
}, null);
return defn ? {id: defn[0], definition: defn[1]} : null;
}
function find_datum(id) {
var defn = pj_datums.reduce(function(memo, arr) {
return arr[0] === id ? arr : memo;
}, null);
return defn ? {id: defn[0], defn: defn[1], ellipse_id: defn[2], name: defn[3]} : null;
}
function pj_datum_set(P) {
var SEC_TO_RAD = 4.84813681109535993589914102357e-6;
var params = P.datum_params = [0,0,0,0,0,0,0];
var name, datum, nadgrids, catalog, towgs84;
P.datum_type = PJD_UNKNOWN;
if (name = pj_param(P.params, 'sdatum')) {
datum = find_datum(name);
if (!datum) {
error(-9);
}
if (datum.ellipse_id) {
pj_mkparam(P.params, 'ellps=' + datum.ellipse_id);
}
if (datum.defn) {
pj_mkparam(P.params, datum.defn);
}
}
nadgrids = pj_param(P.params, "snadgrids");
if (nadgrids && nadgrids != '@null') {
fatal("+nadgrids is not implemented");
}
if (catalog = pj_param(P.params, "scatalog")) {
fatal("+catalog is not implemented");
}
if (towgs84 = pj_param(P.params, "stowgs84")) {
towgs84.split(',').forEach(function(s, i) {
params[i] = pj_atof(s) || 0;
});
if (params[3] != 0 || params[4] != 0 || params[5] != 0 || params[6] != 0) {
P.datum_type = PJD_7PARAM;
params[3] *= SEC_TO_RAD;
params[4] *= SEC_TO_RAD;
params[5] *= SEC_TO_RAD;
params[6] = params[6] / 1e6 + 1;
} else {
P.datum_type = PJD_3PARAM;
/* Note that pj_init() will later switch datum_type to
PJD_WGS84 if shifts are all zero, and ellipsoid is WGS84 or GRS80 */
}
}
}
var pj_ellps = [
// id major ell name
["MERIT", "a=6378137.0", "rf=298.257", "MERIT 1983"],
["SGS85", "a=6378136.0", "rf=298.257", "Soviet Geodetic System 85"],
["GRS80", "a=6378137.0", "rf=298.257222101", "GRS 1980(IUGG, 1980)"],
["IAU76", "a=6378140.0", "rf=298.257", "IAU 1976"],
["airy", "a=6377563.396", "b=6356256.910", "Airy 1830"],
["APL4.9", "a=6378137.0", "rf=298.25", "Appl. Physics. 1965"],
["NWL9D", "a=6378145.0", "rf=298.25", "Naval Weapons Lab., 1965"],
["mod_airy", "a=6377340.189", "b=6356034.446", "Modified Airy"],
["andrae", "a=6377104.43", "rf=300.0", "Andrae 1876 (Den., Iclnd.)"],
["aust_SA", "a=6378160.0", "rf=298.25", "Australian Natl & S. Amer. 1969"],
["GRS67", "a=6378160.0", "rf=298.2471674270", "GRS 67(IUGG 1967)"],
["bessel", "a=6377397.155", "rf=299.1528128", "Bessel 1841"],
["bess_nam", "a=6377483.865", "rf=299.1528128", "Bessel 1841 (Namibia)"],
["clrk66", "a=6378206.4", "b=6356583.8", "Clarke 1866"],
["clrk80", "a=6378249.145", "rf=293.4663", "Clarke 1880 mod."],
["clrk80ign", "a=6378249.2", "rf=293.4660212936269", "Clarke 1880 (IGN)."],
["CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"],
["delmbr", "a=6376428", "rf=311.5", "Delambre 1810 (Belgium)"],
["engelis", "a=6378136.05", "rf=298.2566", "Engelis 1985"],
["evrst30", "a=6377276.345", "rf=300.8017", "Everest 1830"],
["evrst48", "a=6377304.063", "rf=300.8017", "Everest 1948"],
["evrst56", "a=6377301.243", "rf=300.8017", "Everest 1956"],
["evrst69", "a=6377295.664", "rf=300.8017", "Everest 1969"],
["evrstSS", "a=6377298.556", "rf=300.8017", "Everest (Sabah & Sarawak)"],
["fschr60", "a=6378166", "rf=298.3", "Fischer (Mercury Datum) 1960"],
["fschr60m", "a=6378155", "rf=298.3", "Modified Fischer 1960"],
["fschr68", "a=6378150", "rf=298.3", "Fischer 1968"],
["helmert", "a=6378200", "rf=298.3", "Helmert 1906"],
["hough", "a=6378270.0", "rf=297", "Hough"],
["intl", "a=6378388.0", "rf=297", "International 1909 (Hayford)"],
["krass", "a=6378245.0", "rf=298.3", "Krasovsky 1940"], // Proj.4 has "Krassovsky, 1942"
["kaula", "a=6378163", "rf=298.24", "Kaula 1961"],
["lerch", "a=6378139", "rf=298.257", "Lerch 1979"],
["mprts", "a=6397300", "rf=191", "Maupertius 1738"],
["new_intl", "a=6378157.5", "b=6356772.2", "New International 1967"],
["plessis", "a=6376523", "b=6355863", "Plessis 1817 (France)"],
["SEasia", "a=6378155.0", "b=6356773.3205", "Southeast Asia"],
["walbeck", "a=6376896.0", "b=6355834.8467", "Walbeck"],
["WGS60", "a=6378165.0", "rf=298.3", "WGS 60"],
["WGS66", "a=6378145.0", "rf=298.25", "WGS 66"],
["WGS72", "a=6378135.0", "rf=298.26", "WGS 72"],
["WGS84", "a=6378137.0", "rf=298.257223563", "WGS 84"],
["sphere", "a=6370997.0", "b=6370997.0", "Normal Sphere (r=6370997)"],
[null, null, null, null]
];
function find_ellps(id) {
var defn = pj_ellps.reduce(function(memo, arr) {
return arr[0] === id ? arr : memo;
}, null);
return defn ? {id: defn[0], major: defn[1], ell: defn[2], name: defn[3]} : null;
}
function pj_ell_set(P) {
var SIXTH = 0.1666666666666666667, /* 1/6 */
RA4 = 0.04722222222222222222, /* 17/360 */
RA6 = 0.02215608465608465608, /* 67/3024 */
RV4 = 0.06944444444444444444, /* 5/72 */
RV6 = 0.04243827160493827160; /* 55/1296 */
var params = P.params;
var a = 0;
var es = 0;
var name, ellps, tmp, b, i;
if (pj_param(params, 'tR')) {
a = pj_param(params, 'dR');
} else {
if (name = pj_param(params, 'sellps')) {
ellps = find_ellps(name);
if (!ellps) {
error(-9);
}
pj_mkparam(params, ellps.major);
pj_mkparam(params, ellps.ell);
}
a = pj_param(params, 'da');
if (pj_param(params, 'tes')) {
es = pj_param(params, 'des');
} else if (pj_param(params, 'te')) {
tmp = pj_param(params, 'de');
es = tmp * tmp;
} else if (pj_param(params, 'trf')) {
tmp = pj_param(params, 'drf');
if (!tmp) {
error(-10);
}
tmp = 1 / tmp;
es = tmp * (2 - tmp);
} else if (pj_param(params, 'tf')) {
tmp = pj_param(params, 'df');
es = tmp * (2 - tmp);
} else if (pj_param(params, 'tb')) {
b = pj_param(params, 'db');
es = 1 - (b * b) / (a * a);
}
if (!b) {
b = a * sqrt(1 - es);
}
if (pj_param(params, 'bR_A')) {
a *= 1 - es * (SIXTH + es * (RA4 + es * RA6));
es = 0;
} else if (pj_param(params, 'bR_V')) {
a *= 1 - es * (SIXTH + es * (RV4 + es * RV6));
} else if (pj_param(params, 'bR_a')) {
a = 0.5 * (a + b);
es = 0;
} else if (pj_param(params, 'bR_g')) {
a = sqrt(a * b);
es = 0;
} else if (pj_param(params, 'bR_h')) {
if (a + b === 0) {
error(-20);
}
a = 2 * a * b / (a + b);
es = 0;
} else if (i = pj_param(params, 'tR_lat_a') || pj_param(params, 'tR_lat_g')) {
tmp = sin(pj_param(params, i ? 'rR_lat_a' : 'rR_lat_g'));
if (fabs(tmp) > M_HALFPI) {
error(-11);
}
tmp = 1 - es * tmp * tmp;
a *= i ? 0.5 * (1 - es + tmp) / (tmp * sqrt(tmp)) : sqrt(1 - es) / tmp;
es = 0;
}
}
if (es < 0) error(-12);
if (a <= 0) error(-13);
P.es = es;
P.a = a;
}
var pj_units = [
// id to_meter name
["km", "1000", "Kilometer"],
["m", "1", "Meter"],
["dm", "1/10", "Decimeter"],
["cm", "1/100", "Centimeter"],
["mm", "1/1000", "Millimeter"],
["kmi", "1852.0", "International Nautical Mile"],
["in", "0.0254", "International Inch"],
["ft", "0.3048", "International Foot"],
["yd", "0.9144", "International Yard"],
["mi", "1609.344", "International Statute Mile"],
["fath", "1.8288", "International Fathom"],
["ch", "20.1168", "International Chain"],
["link", "0.201168", "International Link"],
["us-in", "1/39.37", "U.S. Surveyor's Inch"],
["us-ft", "0.304800609601219", "U.S. Surveyor's Foot"],
["us-yd", "0.914401828803658", "U.S. Surveyor's Yard"],
["us-ch", "20.11684023368047", "U.S. Surveyor's Chain"],
["us-mi", "1609.347218694437", "U.S. Surveyor's Statute Mile"],
["ind-yd", "0.91439523", "Indian Yard"],
["ind-ft", "0.30479841", "Indian Foot"],
["ind-ch", "20.11669506", "Indian Chain"],
[null, null, null]
];
function find_units_by_value(val) {
return pj_units.reduce(function(memo, defn) {
if (val == +defn[1]) {
memo = find_units(defn[0]);
}
return memo;
}, null);
}
function find_units(id) {
var arr = pj_units.reduce(function(memo, defn) {
return id === defn[0] ? defn : memo;
}, null);
return arr ? {id: arr[0], to_meter: arr[1], name: arr[2]} : null;
}
var initcache = {};
function pj_search_initcache(key) {
return initcache[key.toLowerCase()] || null;
}
function pj_insert_initcache(key, defn) {
initcache[key.toLowerCase()] = defn;
}
// Replacement functions for Proj.4 pj_open_lib() (see pj_open_lib.c)
// and get_opt() (see pj_init.c)
var libcache = {};
// add a definition library without reading from a file (for use by web app)
function mproj_insert_libcache(libId, contents) {
libcache[libId] = contents;
}
function mproj_search_libcache(libId) {
return libcache[libId] || null;
}
function mproj_read_lib_anycase(libFile) {
var fs = require('fs'),
path = require('path'),
// path to library assumes mproj script is in the dist/ directory
dir = path.join(path.dirname(__filename), '../nad'),
pathUC = path.join(dir, libFile.toUpperCase()),
pathLC = path.join(dir, libFile.toLowerCase()),
contents;
if (fs.existsSync(pathUC)) {
contents = fs.readFileSync(pathUC, 'utf8');
} else if (fs.existsSync(pathLC)) {
contents = fs.readFileSync(pathLC, 'utf8');
} else {
fatal('unable to read from \'init\' file named ' + libFile); // not in Proj.4
}
return contents;
}
// Return opts from a section of a config file,
// or null if not found or unable to read file
function pj_read_init_opts(initStr) {
var parts = initStr.split(':'),
libId = parts[0],
crsId = parts[1],
libStr, o;
if (!crsId || !libId) {
error(-3);
}
libId = libId.toLowerCase(); // not in Proj.4
libStr = mproj_search_libcache(libId);
if (!libStr) {
libStr = mproj_read_lib_anycase(libId);
libcache[libId] = libStr;
}
return libStr ? pj_find_opts(libStr, crsId) : null;
}
// Find params in contents of an init file
function pj_find_opts(contents, id) {
var opts = '', comment = '',
idx, idx2;
// get requested parameters
idx = contents.indexOf('<' + id + '>');
if (idx > -1) {
// get comment text
idx2 = contents.lastIndexOf('#', idx);
if (idx2 > -1) {
comment = contents.substring(idx2 + 1, idx).trim();
if (/\n/.test(comment)) {
comment = '';
}
}
// get projection params
opts = contents.substr(idx + id.length + 2);
opts = opts.substr(0, opts.indexOf('<'));
// remove comments
opts = opts.replace(/#.*/g, '');
// convert all whitespace to single <sp>
opts = opts.replace(/[\s]+/g, ' ');
// if '+' is missing from args, add it
// kludge: protect spaces in +title= opts
opts = opts.replace(/\+title=[^+]*[^ +]/g, function(match) {
return match.replace(/ /g, '\t');
});
opts = ' ' + opts;
opts = opts.replace(/ (?=[a-z])/ig, ' +');
opts = opts.replace(/\t/g, ' ').trim();
}
return opts ? {opts: opts, comment: comment} : null;
}
// Returns an initialized projection object
// @args a proj4 string
function pj_init(args) {
var params = pj_get_params(args);
var P = {
params: params,
is_latlong: false,
is_geocent: false,
is_long_wrap_set: false,
long_wrap_center: 0,
axis: "enu",
gridlist: null,
gridlist_count: 0,
vgridlist_geoid: null,
vgridlist_geoid_count: 0
};
var name, defn;
if (!Object.keys(params).length) {
error(-1);
}
if (pj_param(params, "tinit")) {
get_init(params, pj_param(params, "sinit"));
}
name = pj_param(params, "sproj");
if (!name) {
error(-4);
}
defn = pj_list[name];
if (!defn) {
error(-5);
}
if (!pj_param(params, "bno_defs")) {
get_defaults(P.params, name);
}
pj_datum_set(P);
pj_ell_set(P);
P.a_orig = P.a;
P.es_orig = P.es;
P.e = sqrt(P.es);
P.ra = 1 / P.a;
P.one_es = 1 - P.es;
if (!P.one_es) {
error(-6);
}
P.rone_es = 1 / P.one_es;
if (is_wgs84(P)) {
P.datum_type = PJD_WGS84;
}
P.geoc = !!P.es && pj_param(params, 'bgeoc');
P.over = pj_param(params, 'bover');
P.has_geoid_vgrids = pj_param(params, 'tgeoidgrids');
if (P.has_geoid_vgrids) {
pj_param(params, "sgeoidgrids"); // mark as used
}
P.is_long_wrap_set = pj_param(params, 'tlon_wrap');
if (P.is_long_wrap_set) {
P.long_wrap_center = pj_param(params, 'rlon_wrap');
// Don't accept excessive values otherwise we might perform badly
// when correcting longitudes around it
// The test is written this way to error on long_wrap_center "=" NaN
if (fabs(P.long_wrap_center) < 10 * M_TWOPI === false) {
error(-14);
}
}
if (pj_param(params, 'saxis')) {
init_axis(P);
}
P.lam0 = pj_param(params, 'rlon_0');
P.phi0 = pj_param(params, 'rlat_0');
P.x0 = pj_param(params, 'dx_0');
P.y0 = pj_param(params, 'dy_0');
if (pj_param(params, 'tk_0')) {
P.k0 = pj_param(params, 'dk_0');
} else if (pj_param(params, 'tk')) {
P.k0 = pj_param(params, 'dk');
} else {
P.k0 = 1;
}
if (P.k0 <= 0) {
error(-31);
}
init_units(P);
init_prime_meridian(P);
defn.init(P);
return P;
}
// Merge default params
// NOTE: Proj.4 loads defaults from the file nad/proj_def.dat
// This function applies the default ellipsoid from proj_def.dat but
// ignores the other defaults, which could be considered undesirable
// (see e.g. https://github.com/OSGeo/proj.4/issues/201)
function get_defaults(params, name) {
get_opt(params, '+ellps=WGS84');
}
function get_init(params, initStr) {
var defn = pj_search_initcache(initStr);
if (!defn) {
defn = pj_read_init_opts(initStr);
pj_insert_initcache(initStr, defn);
}
if (!defn) {
error(-2);
}
// merge init params
get_opt(params, defn.opts);
}
// Merge params from a proj4 string
// (Slightly different interface from Proj.4 get_opts())
function get_opt(params, args) {
var newParams = pj_get_params(args);
var geoIsSet = ['datum', 'ellps', 'a', 'b', 'rf', 'f'].reduce(function(memo, key) {
return memo || key in params;
}, false);
Object.keys(newParams).forEach(function(key) {
// don't override existing params
if (key in params) return;
// don't set ellps if earth model info is set
if (key == 'ellps' && geoIsSet) return;
params[key] = newParams[key];
});
}
function init_prime_meridian(P) {
var params = P.params,
name, pm, offs;
name = pj_param(params, 'spm');
if (name) {
pm = find_prime_meridian(name);
offs = dmstor(pm ? pm.definition : name);
if (isNaN(offs)) {
error(-46);
}
P.from_greenwich = offs;
} else {
P.from_greenwich = 0;
}
}
function init_units(P) {
var params = P.params;
var name, s, units;
if (name = pj_param(params, 'sunits')) {
units = find_units(name);
if (!units) {
error(-7);
}
s = units.to_meter;
}
if (s || (s = pj_param(params, 'sto_meter'))) {
P.to_meter = parse_to_meter(s);
P.fr_meter = 1 / P.to_meter;
} else {
P.to_meter = P.fr_meter = 1;
}
// vertical units
s = null;
if (name = pj_param(params, 'svunits')) {
units = find_units(name);
if (!units) {
error(-7);
}
s = units.to_meter;
}
if (s || (pj_param(params, 'svto_meter'))) {
P.vto_meter = parse_to_meter(s);
P.vfr_meter = 1 / P.vto_meter;
} else {
P.vto_meter = P.to_meter;
P.vfr_meter = P.fr_meter;
}
}
function parse_to_meter(s) {
var parts = s.split('/');
var val = pj_strtod(parts[0]);
if (parts.length > 1) {
val /= pj_strtod(parts[1]);
}
return val;
}
function init_axis(P) {
var axis_legal = "ewnsud";
var axis = pj_param(P.params, 'saxis');
if (axis.length != 3) {
error(PJD_ERR_AXIS);
}
if (axis_legal.indexOf(axis[0]) == -1 ||
axis_legal.indexOf(axis[1]) == -1 ||
axis_legal.indexOf(axis[2]) == -1) {
error(PJD_ERR_AXIS);
}
P.axis = axis;
}
function is_wgs84(P) {
return P.datum_type == PJD_3PARAM &&
P.datum_params[0] == P.datum_params[1] == P.datum_params[2] === 0 &&
P.a == 6378137 && Math.abs(P.es - 0.006694379990) < 0.000000000050;
}
// TODO: remove error codes (Proj.4 doesn't do anything with them)
var GEOCENT_NO_ERROR = 0x0000,
GEOCENT_LAT_ERROR = 0x0001,
GEOCENT_LON_ERROR = 0x0002,
GEOCENT_A_ERROR = 0x0004,
GEOCENT_B_ERROR = 0x0008,
GEOCENT_A_LESS_B_ERROR = 0x0010;
// a: Semi-major axis, in meters.
// b: Semi-minor axis, in meters.
function pj_Set_Geocentric_Parameters(a, b) {
var err = GEOCENT_NO_ERROR,
a2 = a * a,
b2 = b * b;
if (a <= 0.0) err |= GEOCENT_A_ERROR;
if (b <= 0.0) err |= GEOCENT_B_ERROR;
if (a < b) err |= GEOCENT_A_LESS_B_ERROR;
return err ? null : {
a: a,
b: b,
a2: a2,
b2: b2,
e2: (a2 - b2) / a2,
ep2: (a2 - b2) / b2
};
}
function pj_Convert_Geodetic_To_Geocentric(gi, i, xx, yy, zz) {
var err = GEOCENT_NO_ERROR,
lng = xx[i],
lat = yy[i],
height = zz[i],
x, y, z,
rn, sinlat, sin2lat, coslat;
if (lat < -M_HALFPI && lat > -1.001 * M_HALFPI) {
lat = -M_HALFPI;
} else if (lat > M_HALFPI && lat < 1.001 * M_HALFPI) {
lat = M_HALFPI;
} else if (lat < -M_HALFPI || lat > M_HALFPI) {
err |= GEOCENT_LAT_ERROR;
}
if (!err) {
if (lng > M_PI) lng -= 2 * M_PI;
sinlat = sin(lat);
coslat = cos(lat);
sin2lat = sinlat * sinlat;
rn = gi.a / sqrt(1 - gi.e2 * sin2lat);
xx[i] = (rn + height) * coslat * cos(lng);
yy[i] = (rn + height) * coslat * sin(lng);
zz[i] = ((rn * (1 - gi.e2)) + height) * sinlat;
}
return err;
}
function pj_Convert_Geocentric_To_Geodetic(gi, i, xx, yy, zz) {
var EPS = 1e-12,
EPS2 = EPS * EPS,
MAXITER = 30,
x = xx[i],
y = yy[i],
z = zz[i],
lat, lng, height,
p, rr, ct, st, rx, rn, rk, cphi0, sphi0, cphi, sphi, sdphi, iter;
p = sqrt(x * x + y * y);
rr = sqrt(x * x + y * y + z * z);
if (p / gi.a < EPS) {
lng = 0;
if (rr / gi.a < EPS) {
xx[i] = 0;
yy[i] = M_HALFPI;
zz[i] = -gi.b;
return 0;
}
} else {
lng = atan2(y, x);
}
ct = z / rr;
st = p / rr;
rx = 1 / sqrt(1 - gi.e2 * (2 - gi.e2) * st * st);
cphi0 = st * (1 - gi.e2) * rx;
sphi0 = ct * rx;
iter = 0;
do {
iter++;
rn = gi.a / sqrt(1 - gi.e2 * sphi0 * sphi0);
height = p * cphi0 + z * sphi0 - rn * (1 - gi.e2 * sphi0 * sphi0);
rk = gi.e2 * rn / (rn + height);
rx = 1 / sqrt(1 - rk * (2 - rk) * st * st);
cphi = st * (1 - rk) * rx;
sphi = ct * rx;
sdphi = sphi * cphi0 - cphi * sphi0;
cphi0 = cphi;
sphi0 = sphi;
} while (sdphi * sdphi > EPS2 && iter < MAXITER);
lat = atan(sphi / fabs(cphi));
xx[i] = lng;
yy[i] = lat;
zz[i] = height;
}
// A convenience function for transforming a single point (not in Proj.4)
// @p an array containing [x, y] or [x, y, z] coordinates
// latlong coordinates are assumed to be in decimal degrees
function pj_transform_point(srcdefn, dstdefn, p) {
var z = p.length > 2,
xx = [p[0]],
yy = [p[1]],
zz = [z ? p[2] : 0];
if (srcdefn.is_latlong) {
xx[0] *= DEG_TO_RAD;
yy[0] *= DEG_TO_RAD;
}
ctx.last_errno = 0;
pj_transform(srcdefn, dstdefn, xx, yy, zz);
if (ctx.last_errno || xx[0] == HUGE_VAL) {
// throw error if translation fails
fatal(null, {point: p});
}
if (dstdefn.is_latlong) {
xx[0] *= RAD_TO_DEG;
yy[0] *= RAD_TO_DEG;
}
p[0] = xx[0];
p[1] = yy[0];
if (z) p[2] = zz[0];
}
// Transform arrays of coordinates; latlong coords are in radians
// @xx, @yy[, @zz] coordinate arrays
//
function pj_transform(srcdefn, dstdefn, xx, yy, zz) {
var point_count = xx.length;
var lp = {};
var xy = {};
var err, i, tmp;
if (srcdefn.axis != 'enu') {
pj_adjust_axis(srcdefn.axis, false, xx, yy, zz);
}
if (srcdefn.vto_meter != 1 && zz) {
for ( i = 0; i < point_count; i++ )
zz[i] *= srcdefn.vto_meter;
}
// convert to lat/lng, if needed
if (srcdefn.is_geocent) {
if (!zz) {
error(PJD_ERR_GEOCENTRIC);
}
if (srcdefn.to_meter != 1) {
for (i = 0; i < point_count; i++) {
if (xx[i] != HUGE_VAL ) {
xx[i] *= srcdefn.to_meter;
yy[i] *= srcdefn.to_meter;
}
}
}
pj_geocentric_to_geodetic(srcdefn.a_orig, srcdefn.es_orig, xx, yy, zz);
} else if (!srcdefn.is_latlong) {
if (!srcdefn.inv3d && !srcdefn.inv) {
// Proj.4 returns error code -17 (a bug?)
fatal("source projection not invertible");
}
if (srcdefn.inv3d) {
fatal("inverse 3d transformations not supported");
} else {
for (i=0; i<point_count; i++) {
xy.x = xx[i];
xy.y = yy[i];
tmp = pj_inv(xy, srcdefn);
xx[i] = tmp.lam;
yy[i] = tmp.phi;
check_fatal_error(); // Proj.4 is a bit different
}
}
}
if (srcdefn.from_greenwich !== 0) {
for (i=0; i<point_count; i++) {
if (xx[i] != HUGE_VAL) {
xx[i] += srcdefn.from_greenwich;
}
}
}
if (srcdefn.has_geoid_vgrids && zz) {
fatal("vgrid transformation not supported");
}
pj_datum_transform(srcdefn, dstdefn, xx, yy, zz);
if (dstdefn.has_geoid_vgrids && zz) {
fatal("vgrid transformation not supported");
}
if (dstdefn.from_greenwich !== 0) {
for (i=0; i<point_count; i++) {
if (xx[i] != HUGE_VAL) {
xx[i] -= dstdefn.from_greenwich;
}
}
}
if (dstdefn.is_geocent) {
if (!zz) {
error(PJD_ERR_GEOCENTRIC);
}
pj_geodetic_to_geocentric(dstdefn.a_orig, dstdefn.es_orig, xx, yy, zz);
if (dstdefn.fr_meter != 1) {
for (i = 0; i<point_count; i++) {
if (xx[i] != HUGE_VAL) {
xx[i] *= dstdefn.fr_meter;
yy[i] *= dstdefn.fr_meter;
}
}
}
} else if (!dstdefn.is_latlong) {
if (dstdefn.fwd3d) {
fatal("3d transformation not supported");
} else {
for (i=0; i<point_count; i++) {
lp.lam = xx[i];
lp.phi = yy[i];
tmp = pj_fwd(lp, dstdefn);
xx[i] = tmp.x;
yy[i] = tmp.y;
check_fatal_error(); // Proj.4 is a bit different
}
}
} else if (dstdefn.is_latlong && dstdefn.is_long_wrap_set) {
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
while (xx[i] < dstdefn.long_wrap_center - M_PI) {
xx[i] += M_TWOPI;
}
while (xx[i] > dstdefn.long_wrap_center + M_PI) {
xx[i] -= M_TWOPI;
}
}
}
if (dstdefn.vto_meter != 1 && zz) {
for (i=0; i<point_count; i++) {
zz[i] *= dstdefn.vfr_meter;
}
}
if (dstdefn.axis != 'enu') {
pj_adjust_axis(dstdefn.axis, true, xx, yy, zz);
}
return point_count == 1 ? ctx.last_errno : 0;
}
function pj_adjust_axis(axis, denormalize_flag, xx, yy, zz) {
var point_count = xx.length;
var x_in, y_in, z_in = 0;
var i, i_axis, value, target;
if (!denormalize_flag) {
for (i = 0; i < point_count; i++) {
x_in = xx[i];
y_in = yy[i];
if (x_in == HUGE_VAL) continue; // not in Proj.4
if (zz)
z_in = zz[i];
for (i_axis = 0; i_axis < 3; i_axis++) {
if (i_axis == 0)
value = x_in;
else if (i_axis == 1)
value = y_in;
else
value = z_in;
switch (axis[i_axis]) {
case 'e':
xx[i] = value; break;
case 'w':
xx[i] = -value; break;
case 'n':
yy[i] = value; break;
case 's':
yy[i] = -value; break;
case 'u':
if( zz ) zz[i] = value; break;
case 'd':
if( zz ) zz[i] = -value; break;
default:
error(PJD_ERR_AXIS);
}
} /* i_axis */
} /* i (point) */
}
else {/* denormalize */
for (i = 0; i < point_count; i++) {
x_in = xx[i];
y_in = yy[i];
if (x_in == HUGE_VAL) continue; // not in Proj.4
if (zz)
z_in = zz[i];
for (i_axis = 0; i_axis < 3; i_axis++) {
if (i_axis == 2 && !zz)
continue;
if (i_axis == 0)
target = xx;
else if (i_axis == 1)
target = yy;
else
target = zz;
switch (axis[i_axis]) {
case 'e':
target[i] = x_in; break;
case 'w':
target[i] = -x_in; break;
case 'n':
target[i] = y_in; break;
case 's':
target[i] = -y_in; break;
case 'u':
target[i] = z_in; break;
case 'd':
target[i] = -z_in; break;
default:
error(PJD_ERR_AXIS);
}
} /* i_axis */
} /* i (point) */
}
}
function pj_datum_transform(srcdefn, dstdefn, xx, yy, zz) {
var point_count = xx.length;
var src_a, src_es, dst_a, dst_es;
var z_is_temp = false;
/* We cannot do any meaningful datum transformation if either */
/* the source or destination are of an unknown datum type */
/* (ie. only a +ellps declaration, no +datum). This is new */
/* behavior for PROJ 4.6.0 */
if (srcdefn.datum_type == PJD_UNKNOWN || dstdefn.datum_type == PJD_UNKNOWN) {
return;
}
/* Short cut if the datums are identical. */
if (pj_compare_datums(srcdefn, dstdefn)) {
return;
}
src_a = srcdefn.a_orig;
src_es = srcdefn.es_orig;
dst_a = dstdefn.a_orig;
dst_es = dstdefn.es_orig;
/* Create a temporary Z array if one is not provided. */
if (!zz) {
zz = new Float64Array(point_count);
z_is_temp = true;
}
if (srcdefn.datum_type == PJD_GRIDSHIFT) {
fatal("gridshift not implemented");
// pj_apply_gridshift_2()
src_a = SRS_WGS84_SEMIMAJOR;
src_es = SRS_WGS84_ESQUARED;
}
if (dstdefn.datum_type == PJD_GRIDSHIFT) {
dst_a = SRS_WGS84_SEMIMAJOR;
dst_es = SRS_WGS84_ESQUARED;
}
/* Do we need to go through geocentric coordinates? */
if (src_es != dst_es || src_a != dst_a ||
srcdefn.datum_type == PJD_3PARAM || srcdefn.datum_type == PJD_7PARAM ||
dstdefn.datum_type == PJD_3PARAM || dstdefn.datum_type == PJD_7PARAM) {
pj_geodetic_to_geocentric(src_a, src_es, xx, yy, zz);
if (srcdefn.datum_type == PJD_3PARAM || srcdefn.datum_type == PJD_7PARAM) {
pj_geocentric_to_wgs84(srcdefn, xx, yy, zz);
}
if (dstdefn.datum_type == PJD_3PARAM || dstdefn.datum_type == PJD_7PARAM) {
pj_geocentric_from_wgs84(dstdefn, xx, yy, zz);
}
/* Convert back to geodetic coordinates. */
pj_geocentric_to_geodetic(dst_a, dst_es, xx, yy, zz);
/* Apply grid shift to destination if required. */
if (dstdefn.datum_type == PJD_GRIDSHIFT) {
pj_apply_gridshift_2(dstdefn, 1, xx, yy, zz);
}
}
}
// returns true if datums are equivalent
function pj_compare_datums(srcdefn, dstdefn) {
if (srcdefn.datum_type != dstdefn.datum_type) return false;
if (srcdefn.a_orig != dstdefn.a_orig ||
Math.abs(srcdefn.es_orig - dstdefn.es_orig) > 0.000000000050) {
/* the tolerance for es is to ensure that GRS80 and WGS84 are considered identical */
return false;
}
if (srcdefn.datum_type == PJD_3PARAM) {
return (srcdefn.datum_params[0] == dstdefn.datum_params[0] &&
srcdefn.datum_params[1] == dstdefn.datum_params[1] &&
srcdefn.datum_params[2] == dstdefn.datum_params[2]);
}
if (srcdefn.datum_type == PJD_7PARAM) {
return (srcdefn.datum_params[0] == dstdefn.datum_params[0] &&
srcdefn.datum_params[1] == dstdefn.datum_params[1] &&
srcdefn.datum_params[2] == dstdefn.datum_params[2] &&
srcdefn.datum_params[3] == dstdefn.datum_params[3] &&
srcdefn.datum_params[4] == dstdefn.datum_params[4] &&
srcdefn.datum_params[5] == dstdefn.datum_params[5] &&
srcdefn.datum_params[6] == dstdefn.datum_params[6]);
}
if (srcdefn.datum_type == PJD_GRIDSHIFT) {
return pj_param(srcdefn.params, "snadgrids") ==
pj_param(dstdefn.params, "snadgrids");
}
return true;
}
function pj_geocentric_to_wgs84(defn, xx, yy, zz) {
var point_count = xx.length,
pp = defn.datum_params,
Dx_BF = pp[0],
Dy_BF = pp[1],
Dz_BF = pp[2],
x, y, z, Rx_BF, Ry_BF, Rz_BF, M_BF,
i;
if (defn.datum_type == PJD_3PARAM) {
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
xx[i] += Dx_BF;
yy[i] += Dy_BF;
zz[i] += Dz_BF;
}
} else if (defn.datum_type == PJD_7PARAM) {
Rx_BF = pp[3];
Ry_BF = pp[4];
Rz_BF = pp[5];
M_BF = pp[6];
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
x = M_BF * (xx[i] - Rz_BF * yy[i] + Ry_BF * zz[i]) + Dx_BF;
y = M_BF * (Rz_BF * xx[i] + yy[i] - Rx_BF * zz[i]) + Dy_BF;
z = M_BF * (-Ry_BF * xx[i] + Rx_BF * yy[i] + zz[i]) + Dz_BF;
xx[i] = x;
yy[i] = y;
zz[i] = z;
}
}
}
function pj_geocentric_from_wgs84(defn, xx, yy, zz) {
var point_count = xx.length,
pp = defn.datum_params,
Dx_BF = pp[0],
Dy_BF = pp[1],
Dz_BF = pp[2],
x, y, z, Rx_BF, Ry_BF, Rz_BF, M_BF,
i;
if (defn.datum_type == PJD_3PARAM) {
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
xx[i] -= Dx_BF;
yy[i] -= Dy_BF;
zz[i] -= Dz_BF;
}
} else if (defn.datum_type == PJD_7PARAM) {
Rx_BF = pp[3];
Ry_BF = pp[4];
Rz_BF = pp[5];
M_BF = pp[6];
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
x = (xx[i] - Dx_BF) / M_BF;
y = (yy[i] - Dy_BF) / M_BF;
z = (zz[i] - Dz_BF) / M_BF;
xx[i] = x + Rz_BF * y - Ry_BF * z;
yy[i] = -Rz_BF * x + y + Rx_BF * z;
zz[i] = Ry_BF * x - Rx_BF * y + z;
}
}
}
function pj_geocentric_to_geodetic(a, es, xx, yy, zz) {
var point_count = xx.length;
var b, i, gi;
if (es == 0.0)
b = a;
else
b = a * sqrt(1-es);
gi = pj_Set_Geocentric_Parameters(a, b);
if (!gi) {
error(PJD_ERR_GEOCENTRIC);
}
for (i = 0; i < point_count; i++) {
if (xx[i] != HUGE_VAL) {
pj_Convert_Geocentric_To_Geodetic(gi, i, xx, yy, zz);
}
}
}
function pj_geodetic_to_geocentric(a, es, xx, yy, zz) {
var point_count = xx.length,
b, i, gi;
if (es === 0) {
b = a;
} else {
b = a * sqrt(1 - es);
}
gi = pj_Set_Geocentric_Parameters(a, b);
if (!gi) {
error(PJD_ERR_GEOCENTRIC);
}
for (i=0; i<point_count; i++) {
if (xx[i] == HUGE_VAL) continue;
if (pj_Convert_Geodetic_To_Geocentric(gi, i, xx, yy, zz)) {
xx[i] = yy[i] = HUGE_VAL;
}
}
}
function adjlon(lon) {
var SPI = 3.14159265359,
TWOPI = 6.2831853071795864769,
ONEPI = 3.14159265358979323846;
if (fabs(lon) > SPI) {
lon += ONEPI; /* adjust to 0.0.2pi rad */
lon -= TWOPI * floor(lon / TWOPI); /* remove integral # of 'revolutions'*/
lon -= ONEPI; /* adjust back to -pi..pi rad */
}
return lon;
}
function pj_fwd_deg(lp, P) {
var lp2 = {lam: lp.lam * DEG_TO_RAD, phi: lp.phi * DEG_TO_RAD};
return pj_fwd(lp2, P);
}
function pj_fwd(lp, P) {
var xy = {x: 0, y: 0};
var EPS = 1e-12;
var t = fabs(lp.phi) - M_HALFPI;
// if (t > EPS || fabs(lp.lam) > 10) {
if (!(t <= EPS && fabs(lp.lam) <= 10)) { // catch NaNs
pj_ctx_set_errno(-14);
} else {
ctx.last_errno = 0; // clear a previous error
if (fabs(t) <= EPS) {
lp.phi = lp.phi < 0 ? -M_HALFPI : M_HALFPI;
} else if (P.geoc) {
lp.phi = atan(P.rone_es * tan(lp.phi));
}
lp.lam -= P.lam0;
if (!P.over) {
lp.lam = adjlon(lp.lam);
}
if (P.fwd) {
P.fwd(lp, xy);
xy.x = P.fr_meter * (P.a * xy.x + P.x0);
xy.y = P.fr_meter * (P.a * xy.y + P.y0);
} else {
xy.x = xy.y = HUGE_VAL;
}
}
if (ctx.last_errno || !isFinite(xy.x) || !isFinite(xy.y)) {
// isFinite() catches NaN and +/- Infinity but not null
xy.x = xy.y = HUGE_VAL;
}
return xy;
}
function pj_inv_deg(xy, P) {
var lp = pj_inv(xy, P);
return {
lam: lp.lam * RAD_TO_DEG,
phi: lp.phi * RAD_TO_DEG
};
}
function pj_inv(xy, P) {
var EPS = 1e-12;
var lp = {lam: 0, phi: 0};
// if (xy.x == HUGE_VAL || xy.y == HUGE_VAL) {
if (!(xy.x < HUGE_VAL && xy.y < HUGE_VAL)) { // catch NaNs
pj_ctx_set_errno(-15);
} else {
ctx.last_errno = 0;
if (P.inv) {
xy.x = (xy.x * P.to_meter - P.x0) * P.ra;
xy.y = (xy.y * P.to_meter - P.y0) * P.ra;
P.inv(xy, lp);
lp.lam += P.lam0;
if (!P.over) {
lp.lam = adjlon(lp.lam);
}
if (P.geoc && fabs(fabs(lp.phi) - M_HALFPI) > EPS) {
lp.phi = atan(P.one_es * tan(lp.phi));
}
} else {
lp.lam = lp.phi = HUGE_VAL;
}
}
if (ctx.last_errno || !isFinite(lp.lam) || !isFinite(lp.phi)) {
// isFinite() catches NaN and +/- Infinity but not null
lp.lam = lp.phi = HUGE_VAL;
}
return lp;
}
function get_rtodms(decimals, fixedWidth, pos, neg) {
var dtodms = get_dtodms(decimals, fixedWidth, pos, neg);
return function(r) {
return dtodms(r * RAD_TO_DEG);
};
}
// returns function for formatting as DMS
// See Proj.4 rtodms.c
// @pos: 'N' or 'E'
// @neg: 'S' or 'W'
function get_dtodms(decimals, fixedWidth, pos, neg) {
var RES, CONV, i;
if (decimals < 0 || decimals >= 9) {
decimals = 3;
}
RES = 1;
for (i=0; i<decimals; i++) {
RES *= 10;
}
CONV = 3600 * RES;
return function(r) {
var sign = '',
mstr = '',
sstr = '',
min, sec, suff, dstr;
if (r === HUGE_VAL || isNaN(r)) return '';
if (r < 0) {
r = -r;
suff = neg || '';
if (!suff) {
sign = '-';
}
} else {
suff = pos || '';
}
r = floor(r * CONV + 0.5);
sec = (r / RES) % 60;
r = floor(r / (RES * 60));
min = r % 60;
dstr = floor(r / 60) + 'd';
sstr = sec.toFixed(decimals);
sec = parseFloat(sstr);
if (sec) {
sstr = (fixedWidth ? sstr : String(sec)) + '"';
} else {
sstr = '';
}
if (sec || min) {
mstr = String(min) + "'";
if (mstr.length == 2 && fixedWidth) {
mstr = '0' + mstr;
}
}
return sign + dstr + mstr + sstr + suff;
};
}
// Support for the proj4js api:
// proj4(fromProjection[, toProjection, coordinates])
function proj4js(arg1, arg2, arg3) {
var p, fromStr, toStr, P1, P2, transform;
if (typeof arg1 != 'string') {
// E.g. Webpack's require function tries to initialize mproj by calling
// the module function.
return api;
} else if (typeof arg2 != 'string') {
fromStr = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'; // '+datum=WGS84 +proj=lonlat';
toStr = arg1;
p = arg2;
} else {
fromStr = arg1;
toStr = arg2;
p = arg3;
}
P1 = pj_init(fromStr);
P2 = pj_init(toStr);
transform = get_proj4js_transform(P1, P2);
if (p) {
return transform(p);
} else {
return {forward: transform, inverse: get_proj4js_transform(P2, P1)};
}
}
proj4js.WGS84 = '+proj=longlat +datum=WGS84'; // for compatibility with proj4js tests
// for compatibility with proj4js tests
proj4js.toPoint = function(array) {
var out = {
x: array[0],
y: array[1]
};
if (array.length>2) {
out.z = array[2];
}
if (array.length>3) {
out.m = array[3];
}
return out;
};
function get_proj4js_transform(P1, P2) {
return function(p) {
var useArray = Array.isArray(p);
p = useArray ? p.concat() : [p.x, p.y];
pj_transform_point(P1, P2, p);
if (!useArray) {
p = {x: p[0], y: p[1]};
}
return p;
};
}
// Fallback WKT definitions include a Proj.4 string in an EXTENSION property.
// They should be readable by QGIS and gdal/ogr, but will not work
// with most other GIS software.
function get_fallback_wkt_maker(P) {
// TODO: validate P?
return make_fallback_wkt;
}
function make_fallback_wkt(P) {
var projName = P.proj in pj_list ? pj_list[P.proj].name : '';
var proj4 = get_proj_defn(P);
var geogcs = wkt_make_geogcs(P);
// GDAL seems to use "unnamed" all the time
var name = projName ? geogcs.NAME + ' / ' + projName : 'unnamed';
return {PROJCS: {
NAME: name,
GEOGCS: geogcs,
PROJECTION: 'custom_proj4',
PARAMETER: [],
UNIT: wkt_make_unit(P),
EXTENSION: ['PROJ4', proj4 + ' +wktext']
}};
}
function get_fallback_wkt_parser(projcs) {
var proj4 = get_proj4_from_extension(projcs);
// TODO: try parsing proj4 string to validate?
return proj4 ? get_proj4_from_extension : null;
}
function get_proj4_from_extension(projcs) {
var ext = projcs.EXTENSION;
if (ext && ext[0] == 'PROJ4') {
return (ext[1] || '').replace(' +wktext', '');
}
return null;
}
// Global collections of WKT parsers and makers
// arr[0] is test function; arr[1] is conversion function
var wkt_makers = [];
var wkt_parsers = [];
// TODO: use utility library
function wkt_is_object(val) {
return !!val && typeof val == 'object' && !Array.isArray(val);
}
function wkt_is_string(val) {
return typeof val == 'string';
}
function find_wkt_parser(projcs) {
var parser = find_wkt_conversion_function(projcs, wkt_parsers);
if (!parser) {
parser = get_fallback_wkt_parser(projcs);
}
if (!parser) {
wkt_error('unsupported WKT definition: ' + get_wkt_label(projcs));
}
return parser;
}
function find_wkt_maker(P) {
var maker = find_wkt_conversion_function(P, wkt_makers);
if (!maker) {
maker = get_fallback_wkt_maker(P);
}
if (!maker) {
wkt_error('unsupported projection: ' + get_proj_label(P));
}
return maker;
}
function find_wkt_conversion_function(o, arr) {
var is_match;
for (var i=0; i<arr.length; i++) {
is_match = arr[i][0];
if (is_match(o)) return arr[i][1];
}
return null;
}
function get_proj_label(P) {
return get_proj_id(P) || '[unknown]';
}
function get_wkt_label(o) {
return o.NAME || '[unknown]';
}
function get_proj_id(P) {
return pj_param(P.params, 'sproj');
}
function wkt_name_to_slug(name) {