UNPKG

mproj

Version:

A JavaScript port of the Proj.4 cartographic projections library

1,862 lines (1,654 loc) 358 kB
(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) {