"use strict";
// Define a set of functions (and a few global variables) used by sealevel.info web pages
// and tools (mostly about sea-level rise).
// Copyright 2016-2024, by David A. Burton, www.sealevel.info.
// This module is currently used from from one Node.js program, mslcalc.js, and from the following
// HTML & PHP web pages:
// MSL_graph.php, MSL_weighted.php, ch4.html, co2.html, co2_and_ch4.html, boxcar_weights_graph.html
// I pretend there are only browsers and Node.js. (Sorry, Deno, and Oracle Nashorn!)
var ThisIsNode = (('object' === typeof module) && module.exports);
// polyfill globalThis (slightly tested, on v.17 thru 27 of Firefox, on WinXP, via LamdaTest, though Google Charts requires v.28 [circa 3/18/2014] or later)
if ('undefined' === typeof globalThis) {
if ('undefined' !== typeof window) {
var globalThis = window; // presumably a browser
} else if ('undefined' !== typeof global) {
var globalThis = global; // presumably Node.js
} else if ('undefined' !== typeof self) {
var globalThis = self;
} else if ('undefined' !== typeof this) {
var globalThis = this;
} else {
throw new Error('ERROR: Unable to polyfill missing "globalThis"');
}
}
// Check for javascript feature support for the four collection classes, that some browsers lack
var map_supported, set_supported, weakmap_supported, weakset_supported;
if (ThisIsNode) {
// All modern versions of Node.js support all the collection types.
// For other tests see https://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
map_supported = set_supported = weakmap_supported = weakset_supported = true;
} else {
map_supported = ("Map" in window); // this is the important one (hashes / associative arrays)
set_supported = ("Set" in window);
weakmap_supported = ("WeakMap" in window);
weakset_supported = ("WeakSet" in window);
}
var suppress_warnings = false; // if this global is set true, then warn_once() becomes dont_warn
var suppress_console_log = false; // if this global is set true, then don't log dbg info to console
function console_log(str) {
if (!suppress_console_log) {
console.log(str); // writes to stdout
}
}
function my_alert(warning) {
if (ThisIsNode) {
console.warn(warning); // writes to stderr
if (! process.stdout.isTTY) {
console.log(warning); // if stdout has been redirected then it also writes to output file
// (this causes doubled error messages in an editor's process buffer, but there's no helping that)
}
// alert() is not defined in Node.js
} else {
console.warn(warning); // writes to console; in Chrome it is yellow-highlighted
alert(warning);
}
}
// polyfill for IE, which doesn't support array.includes()
// per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement /*, fromIndex*/) {
if (this == null) {
throw new TypeError('Array.prototype.includes called on null or undefined');
}
var O = Object(this);
var len = parseInt(O.length, 10) || 0;
if (len === 0) {
return false;
}
var n = parseInt(arguments[1], 10) || 0;
var k;
if (n >= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {k = 0;}
}
var currentElement;
while (k < len) {
currentElement = O[k];
if (searchElement === currentElement ||
(searchElement !== searchElement && currentElement !== currentElement)) { // NaN !== NaN
return true;
}
k++;
}
return false;
};
}
// Functions defined:
//
// isArray(x) - Tests whether x is an array
// isNumber(x) - Tests whether x is numeric (and not infinity)
// isInteger(x) - Returns true iff x is an integer.
// isEven(n) - n should be an integer; returns true iff it is an even number.
// isString(x) - Tests whether x is a string
// repr(x) - Similar to Python repr() or str(), returns a human-readable representation of x
// repr(x,precision) - Same as repr(x) except specifies number of significant digits for floats
// deepcopy(x) - Makes a copy of x (rather than just copying a reference)
// min(x) - Return the minimum value from the values in 1D array x
// max(x) - Return the maximum value from the values in 1D array x
// avg(x) - Return the average of the values in 1D array x
// vect_add(v1,v2) -- Add the values of the elements of two vectors (one-dimensional arrays).
// vect_sub(v1,v2) -- Subtract the values of the elements of v2 from corresponding elements of v1 (one-dimensional arrays).
// vect_mult(v1,v2) -- Multiply the values of the elements of two vectors (one-dimensional arrays).
// vect_summation(x) - Return the sum of the values in 1D array x
// sum_of_squares(x) - Return the sum of the squares of the values in 1D array x
// sum_in_quadrature(vect) - Sum two or more numbers in quadrature (sqrt of sum of squares)
// deviations(x) - Return 1D array of deviations of each element of x from avg(x)
// decimal_date(yr,mo) - Return a "decimal date" for a specified year & month (1-12), adjusted by global date_rebase_yr
// year_mo_from_decimal_date(decdate) - inverse of decimal_date(), returns [year,month], where month is 1..12
// dd2yr(decdate) - decimal date to year = just the year part of year_mo_from_decimal_date() result
// dd2mo(decdate) - decimal date to month = just the month part of year_mo_from_decimal_date() result
// dd_repr(decdate) - Return printable representation of (rebased) decimal date; e.g., "1955/10" for Oct. 1955.
// ratio_of_std_errs_from_AR1(ar1) - Calculate the ratio of standard errors from autoregressive_coefficient ar1
// y_from_x(x,b,m,a) - Calculate Y for X from a linear or quadratic formula: y = b + m*x + a*x**2
// Ys_from_Xs(Xs,b,m,a) - Calculate Y values for a vector of X values, from a linear or quadratic equation
// basic_linear_regression(Xs,Ys) - Least squares linear regression (without calculation of confidence intervals, R-squared, etc.)
// linear_regression(Xs,Ys) - Least squares linear regression (including calculation of confidence intervals, R-squared, etc.)
// three_unknowns( a1,b1,c1,d1, a2,b2,c2,d2, a3,b3,c3,d3 ) - solve three simultaneous linear equations in three unknowns
// basic_quadratic_regression(Xs,Ys,weights) - Least squares quadratic regression, with optional weights
// find_date_where_quadratic_x_coefficient_matches_linear_slope(values_x,values_y) - At what x does "x coefficient" of the quadratic regression equal slope calculated by linear regression?
// column(i,rows) - Return a copy of one column from a 2D array, as a vector (1D array)
// columns(columnindices,rows) - Return specified columns from a 2D array, as a smaller 2D array
// swap_rows_and_columns(rows) - Swap rows and colums in a 2D array. If input is MxN, output is NxM
// add_columns(left_part,additional_cols) - Add additional columns to a 2D array
// warn_once(warning,warning_tag) - alert(warning), but only once
// this_year() - Returns the current year (four decimal digits)
// eval_int(istr, minval, maxval, defaultval) - Parse a user-specified integer, and ensure it is something reasonable.
// get_Get_parameters_from_URI() - retrieve parameters from after the '?' in the URL/URI
// fixup_url(regex_for_param,new_param,push_rather_than_replace) - adjust URL in browser address bar with updated parameters
// boxcar_smoothing(input_y_vals,len,rpt) - "Boxcar" (moving average) graph smoother
// interpolate_CO2(adate) - return interpolated CO2 level for a specified date
// psmsl_msl(stdt, year, mo) - Fetch PSMSL's value for MSL for a specified year and month (1..12) from global stdt.msl_data[]
// psmsl_rlr_to_noaa(stdt, mo, rlr_m) - Convert PSMSL's reported RLR value (in mm) to NOAA equivalent (in meters)
// calculate_seasonal_cycle_from_psmsl(stdt) - Calculate average seasonal cycle from PSMSL data
// nearly_identical(a,b) - Returns true iff a and be are nearly identical numbers
// nearly_identical(a,b,mul_precision) - same as nearly_identical(a,b), but lets you adjust how "near" the values must be
// nearly_identical(a,b,mul_precision,add_precision) - nearly_identical(a,b,mul_precision), but lets you also adjust the tolerance for values near zero
// residuals(Xs,Ys,b,m,a) - Calculate residuals (difference between actual and calculated Ys)
// loadJSON(filePath) - Synchronously load a .json file on the server with Ajax
// Object constructor defined:
//
// Traces(trace_data_array, trace_names, trace_colors, trace_options) - constructor for a Traces object
// Other globals defined:
//
// var co2_and_ch4[] - 2D array of CO2 & CH4 levels
// var map_supported, set_supported, weakmap_supported, weakset_supported
// var params[] - associative array of GET parameters passed with the URL
// if Map object support is available, use deepcopy2 & repr2; otherwise, fall back to deepcopy1 & repr1
var deepcopy = map_supported ? deepcopy2 : deepcopy1;
var repr = map_supported ? repr2 : repr1;
// 'stdt' is the dictionary for accessing MSL data from NOAA & PSMSL:
// stdt
// .msl_data; // the 2D array of MSL data from NOAA and PSMSL
// .psmsl_to_noaa_offset; // to be defined from the msl_data[], should be about 7000
// .seasonal_cycle_calculated; // to be defined from the msl_data[], by calling calculate_seasonal_cycles_from_psmsl()
// .seasonal_cycle; // 12-element array with data from either http://tidesandcurrents.noaa.gov/sltrends/downloadAvgSeasonalCycleCSV.htm?stnid=... or .seasonal_cycle_calculated
// Is the input parameter an array?
// See http://stackoverflow.com/questions/767486/how-do-you-check-if-a-variable-is-an-array-in-javascript
// and http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
// and http://stackoverflow.com/questions/4775722/check-if-object-is-array
function isArray(x) {
return (x instanceof Array)
// This is the quick test, which works unless x is from a different iframe.
// "The problems arise when it comes to scripting in multi-frame DOM environments.
// In a nutshell, Array objects created within one iframe do not share [[Prototype]]s
// with arrays created within another iframe. Their constructors are different objects
// and so both instanceof and constructor checks fail." http://archive.is/4QK3l#selection-219.0-227.13
|| ( x // note that [] surprisingly evaluates to true (unlike "")
&& ((typeof x) === 'object')
&& ((typeof x.length) === 'number')
&& ((typeof x.splice === 'function')
&& !(x.propertyIsEnumerable('length'))));
// Per Crockford's _JavaScript The Good Parts_ (h/t Yunzhou), who says:
// First, we ask if the value is truthy. We do this to reject null and other falsy
// values. Second, we ask if the typeof value is 'object'. This will be true for
// objects, arrays, and (weirdly) null. Third, we ask if the value has a length
// property that is a number. This will always be true for arrays, but usually not
// for objects. Fourth, we ask if the value contains a splice method. This again will
// be true for all arrays. Finally, we ask if the length property is enumerable (will
// length be produced by a for in loop?). That will be false for all arrays. This is
// the most reliable test for arrayness that I have found. It is unfortunate that it
// is so complicated.
}
// Is x numeric (and not infinity)?
function isNumber(x) {
return ((typeof x) === 'number') && isFinite(x);
}
// Returns true iff x is an integer.
function isInteger(x) {
// return x.isInteger(); // doesn't work in Safari or IE
return Math.floor(x) === x;
}
// n should be an integer; returns true iff it is an even number.
function isEven(n) {
return !(n % 2);
}
// tests whether x is a string
function isString(x) {
return ('string'===typeof x) || (x instanceof String);
}
// sub-function of repr()
function summarizeFunction(f) {
var name = null, result;
if (f.hasOwnProperty('name')) {
name = f.name;
}
if (name) {
result = "function " + name;
} else {
result = "(anonymous function)";
}
result += "()";
return result;
}
// Like repr() or str() in Python, more or less: it produces a human-readable
// representation of its input parameter.
// This version is very simple, and it relies on JSON.stringify() to do the work
// in most cases, so it isn't very robust. I include this version for browsers
// which lack support for the Map object.
function repr1(x, precision) {
var result, typ = typeof x;
if (typ === 'function') {
result = summarizeFunction(x);
} else if (typ === 'undefined') {
result = typ;
} else if (null === x) {
result = 'null';
} else if (x instanceof Date) {
result = 'Date("' + x.toDateString() + ' ' + x.toLocaleTimeString() + '")'; // or could do: result = 'Date(' + JSON.stringify(x) + ')';
} else if (x instanceof RegExp) {
result = x.toString();
} else if (map_supported && (x instanceof Map)) {
result = '';
} else if (set_supported && (x instanceof Set)) {
result = '';
} else if (weakmap_supported && (x instanceof WeakMap)) {
result = '';
} else if (weakset_supported && (x instanceof WeakSet)) {
result = '';
} else if ((null != precision) && isNumber(x) && !isInteger(x)) {
result = x.toPrecision(precision);
} else {
// console_log('typeof x = ' + typ);
try {
result = JSON.stringify(x);
}
catch (er) {
// error on the JSON.stringify() call, perhaps "TypeError: Converting circular structure to JSON"
// console_log('Error, repr1() failed: er="' + er + '", typ=' + typ);
result = '{{' + er + '}}';
}
}
// console_log('JSON.stringify() = ' + result);
return result;
}
// Like repr() or str() in Python, more or less: it produces a human-readable
// representation of its input parameter.
// Unlike just calling JSON.stringify(), this function does reasonable things with
// non-tree-shaped data structures, including circular references.
// Do not call this unless the Map object is supported, because it uses a Map
// (associative array) to detect circular and multiple references.
// The optional 2nd parameter is used to specify the precision for displaying
// floating point numbers, and is ignored for other types.
function repr2(x, precision) {
repr2.recursion_depth++;
var result, i, elem, typ = typeof x;
if (typ === 'function') {
result = summarizeFunction(x);
} else if (typ === 'undefined') {
result = typ;
} else if (null === x) {
result = 'null';
} else if ((typ === 'object') && (repr2.recursion_depth <= 10)) {
result = undefined;
if (1 == repr2.recursion_depth) {
// this is the "outer" (non-recursive) context, so create empty .objs_seen associative array ("map")
repr2.objs_seen = new Map();
} else if (repr2.objs_seen.has(x)) {
// we've already seen this object reference! Could be circular.
result = '[';
}
if (undefined === result) {
repr2.objs_seen.set(x,1); // remember that we've seen this object or array, in case of circular references
var tmp = [];
if (isArray(x)) {
// for (var elem of x) // stupid IE11 doesn't handle this
for (i=0; i result))) {
result = vals[i];
}
}
}
return result;
}
// sum the values of the elements of a vector (a one-dimensional array)
function vect_summation(vals,oops) {
if ((null != oops) || !isArray(vals)) {
throw new Error('ERR: vect_summation() must be called with a single array parameter, not with multiple scalars.');
}
var i, result = 0;
for (i=0; i= 13) { // if mo==13 we change it to 1 and increment the year
yr++;
mo -= 12;
}
while (mo <= 0) { // if mo==0 we change it to 12 and decrement the year
yr--;
mo += 12;
}
var ddate = yr + [0.04,0.12,0.20,0.29,0.37,0.46,0.54,0.62,0.71,0.79,0.87,0.96][mo-1];
// See long discussion below, esp. the "calc[2]" column.
ddate -= date_rebase_yr;
// console_log(`dbg: [slc line ~534] set adjust decimal date by -date_rebase_yr = ${date_rebase_yr}`);
return ddate;
}
// Inverse of decimal_date(), returns [year,month], where month is 1..12,
// and year is A.D. (typically 1800..2021).
function year_mo_from_decimal_date(decdate) {
var yr, mo, remainder;
yr = Math.floor(decdate);
remainder = decdate - yr;
yr += date_rebase_yr;
// console_log(`dbg: [slc line ~545] calc yr ${yr} from decimal date by adding date_rebase_yr=${date_rebase_yr}`);
mo = 1 + Math.floor(remainder * 12);
if ((mo < 1) || (mo > 12)) { throw new Error('ERR: year_mo_from_decimal_date calculated month=' + mo + ', which is not in 1..12'); }
return [yr,mo];
}
// Decimal date to year = just the year part of year_mo_from_decimal_date() result
function dd2yr(decdate) {
var tmp = year_mo_from_decimal_date(decdate);
return tmp[0];
}
// Decimal date to month = just the month part of year_mo_from_decimal_date() result
function dd2mo(decdate) {
var tmp = year_mo_from_decimal_date(decdate);
return tmp[1];
}
// Return printable representation of (rebased) decimal date; e.g., "1955/10" for Oct. 1955.
// Note that it's just a year and month, except for the special case of an integer dd value,
// which is displayed with ".0" to emphasize that it is 1/1/yyyy instead of a mid-month.
function dd_repr(dd) {
var result = '' + dd2yr(dd);
if (dd == Math.floor(dd)) {
result += ".0";
} else {
result += ('/' + dd2mo(dd));
}
return result;
}
// console_log('dbg: testing dd_repr()...');
// if ('1800.0' != dd_repr(0.0)) {
// my_alert('ERR: dd_repr(0.0) should be 1800.0, but is actually ' + dd_repr(0.0));
// }
// if ('1802/2' != dd_repr(2.12)) {
// my_alert('ERR: dd_repr(2.12) should be 1802/2, but is actually ' + dd_repr(2.12));
// }
// console_log('dbg: done testing dd_repr.');
// "Decimal dates" for mid-months are not precise, in part because months vary
// in length, and February varies in length from year to year.
// PSMSL does it strangely: they start with the approximation that every month
// is 1/12 of a year, and then they round to 3-3/4 digits, forcing the 4th digit
// to be even.
// Below I've calculated it more precisely, taking into account the number of
// days in each month, but approximating February as 28.25 days. When rounded
// to three digits, it agrees with PSMSL's dates two months out of twelve. When
// rounded to two digits, it agrees with PSMSL's dates seven months out of twelve, 3-digits rounded 2-digits rounded
// but the other five only differ in the last digit. ---------------- ----------------
// PSMSL 1/12-mo mid-month calculation calculated PSMSL[3] calc[3] PSMSL[2] calc[2]
// Jan 0.0416 0.041667 1/16 15.5/365.25= 0.04244 0.042 0.042 0.04 0.04
// Feb 0.1250 0.125000 2/14.6125 (31+14.125)/365.25= 0.12355 0.125 0.124 0.13 0.12
// Mar 0.2084 0.208333 3/16 (31+28.25+15.5)/365.25= 0.20465 0.208 0.205 0.21 0.20
// Apr 0.2916 0.291667 4/15.5 (31+28.25+31+15)/365.25= 0.28816 0.292 0.288 0.29 0.29
// May 0.3750 0.375000 5/16 (31+28.25+31+30+15.5)/365.25= 0.37166 0.375 0.372 0.38 0.37
// Jun 0.4584 0.458333 6/15.5 (31+28.25+31+30+31+15)/365.25= 0.45517 0.458 0.455 0.46 0.46
// Jul 0.5416 0.541667 7/16 (31+28.25+31+30+31+30+15.5)/365.25= 0.53867 0.542 0.539 0.54 0.54
// Aug 0.6250 0.625000 8/16 (31+28.25+31+30+31+30+31+15.5)/365.25= 0.62355 0.625 0.624 0.63 0.62
// Sep 0.7084 0.708333 9/15.5 (31+28.25+31+30+31+30+31+31+15)/365.25= 0.70705 0.708 0.707 0.71 0.71
// Oct 0.7916 0.791667 10/16 (31+28.25+31+30+31+30+31+31+30+15.5)/365.25= 0.79055 0.792 0.791 0.79 0.79
// Nov 0.8750 0.875000 11/15.5 (31+28.25+31+30+31+30+31+31+30+31+15)/365.25= 0.87406 0.875 0.874 0.88 0.87
// Dec 0.9584 0.958333 12/16 (31+28.25+31+30+31+30+31+31+30+31+30+15.5)/365.25= 0.95756 0.958 0.958 0.96 0.96
// " " " " = (365.25-15.5)/365.25= 0.95756 -------
//
// Note: to see how leap years affect the calculation consider, for example, October:
// Oct .7916 10/16 (31+28.25+31+30+31+30+31+31+30+15.5)/365.25= 0.79055
// non-leap year: (31+28+31+30+31+30+31+31+30+15.5)/365= 0.79041
// leap-year: (31+29+31+30+31+30+31+31+30+15.5)/366= 0.79098
// As you can see, the inaccuracy introduced by treating leap & non-leap years
// the same leaves us with just three significant digits after the decimal point.
// Test it:
// var tm1 = decimal_date(1941,12,7);
// var tm2 = year_mo_from_decimal_date(tm1);
// if ((tm2[0] != 1941) || (tm2[1] != 12)) { my_alert('ERR: year_mo_from_decimal_date miscalculated!'); }
// Add the values of the elements of two vectors (one-dimensional arrays).
// v1 must be a vector. v2 may be either a vector or a scalar.
function vect_add(v1, v2) {
var i, result = [], len = v1.length;
if (isArray(v2)) {
if (len != v2.length) {
warn_once('ERR: vect_add( v1.length=' + len + ', != v2.length=' + v2.length + ')', 'vect_add');
len = min(len,v2.length);
}
for (i=0; i 2) {
// Calculate R-squared ("correlation") (per comment on JanS's code by "Jaimin") -- this is untested
var denom = (count*sum_xx - sum_x*sum_x) * (count*sum_yy - sum_y*sum_y);
if (denom > 0) {
denom = Math.sqrt(denom);
var numer = count*sum_xy - sum_x*sum_y;
r2 = Math.pow( numer/denom, 2 );
}
// std error of slope (a/k/a Std Deviation)
// se_m = sqrt( sum((y-avg(y))**2)/(n-2) ) / sqrt( sum((x-avg(x))**2) )
// ref: http://stattrek.com/regression/slope-confidence-interval.aspx?Tutorial=AP
var calc_y, dev_x, dev_y;
var sum_sq_dev_x = 0;
var SSres = 0; // y=MSL; SSres = sum of squared residuals (residuals are the differences between predicted y and measured y)
for (i = 0; i < len; i++) {
x = values_x[i];
dev_x = x - avg_x;
y = values_y[i]; // measured
calc_y = y_from_x( x, b, m ); // predicted value: calc_y = (m * x) + b;
dev_y = y - calc_y;
sum_sq_dev_x += (dev_x*dev_x);
SSres += (dev_y*dev_y);
}
se_b = Math.sqrt(SSres/(count-2));
if (sum_sq_dev_x > 0) {
// first calculate the std error of the slope, m
se_m = se_b / Math.sqrt(sum_sq_dev_x);
// console_log('se_m=' + se_m);
}
// The 95% confidence interval range is calculated as the product of that value and t*, or, in Excel, TINV(0.05,count-2)
ci95_m = se_m * 1.96; // http://stattrek.com/online-calculator/t-distribution.aspx says 1.962 for Wilmington (from: deg of freedom 959, cumulative probablity .975)
// now calculate the confidence interval of y at x=avg(x)
// console_log('se_b=' + se_b + ', SSres=' + SSres + ', n=' + (count-2));
ci95_b = 1.96 * se_b * Math.sqrt(1 / count);
// 1.96 = t*, or, in Excel, TINV(0.05,count-2). http://stattrek.com/online-calculator/t-distribution.aspx says 1.962 (from: deg of freedom 959, cumulative probablity .975)
}
if (count != len) {
throw new Error("ERR: count != len, should be impossible.");
}
}
return [b, m, ci95_m, r2, se_m, avg_x, avg_y, ci95_b, se_b];
}//linear_regression()
// Adapted from http://www.1728.com/unknwn3.htm (in JavaScript) by Robert
// Gagnon, a/k/a wolf_meister, for my Perl version. Later, I translated from
// the Perl version back into Javascript.
//
// Given the 12 coefficients (A1,B1,C1,D1,A2,...,D3) of these 3 equations:
//
// A1*x + B1*y + C1*z = D1
// A2*x + B2*y + C2*z = D2
// A3*x + B3*y + C3*z = D3
//
// Solve for the 3 unknowns (x,y,z):
//
// // calculate the "delta" determinant
// delta = (A1*B2*C3)+(B1*C2*A3)+(C1*A2*B3)-(C1*B2*A3)-(A1*C2*B3)-(B1*A2*C3)
//
// // calculate the X numerator & answer
// xnum = (D1*B2*C3)+(B1*C2*D3)+(C1*D2*B3)-(C1*B2*D3)-(D1*C2*B3)-(B1*D2*C3)
// x = xnum/delta
//
// // calculate the Y numerator & answer
// ynum = (A1*D2*C3)+(D1*C2*A3)+(C1*A2*D3)-(C1*D2*A3)-(A1*C2*D3)-(D1*A2*C3)
// y = ynum/delta
//
// // calculate the Z numerator & answer
// znum = (A1*B2*D3)+(B1*D2*A3)+(D1*A2*B3)-(D1*B2*A3)-(A1*D2*B3)-(B1*A2*D3)
// z = znum/delta
//
// Given the 12 coefficients of the 3 linear functions on [x,y,z], calculate
// and return [x,y,z]
function three_unknowns( a1,b1,c1,d1, a2,b2,c2,d2, a3,b3,c3,d3 ) {
var delta, xnum, ynum, znum, x, y, z;
// calculate the "delta" determinant
delta = (a1 * b2 * c3) + (b1 * c2 * a3) + (c1 * a2 * b3) - (c1 * b2 * a3) - (a1 * c2 * b3) - (b1 * a2 * c3);
if (0 == delta) {
throw new Error( "ERROR: division-by-zero!" );
x = y = z = 0;
} else {
// calculate the X numerator & answer
xnum = (d1 * b2 * c3) + (b1 * c2 * d3) + (c1 * d2 * b3) - (c1 * b2 * d3) - (d1 * c2 * b3) - (b1 * d2 * c3);
x = xnum/delta;
// calculate the Y numerator & answer
ynum = (a1 * d2 * c3) + (d1 * c2 * a3) + (c1 * a2 * d3) - (c1 * d2 * a3) - (a1 * c2 * d3) - (d1 * a2 * c3);
y = ynum/delta;
// calculate the Z numerator & answer
znum = (a1 * b2 * d3) + (b1 * d2 * a3) + (d1 * a2 * b3) - (d1 * b2 * a3) - (a1 * d2 * b3) - (b1 * a2 * d3);
z = znum/delta;
}
return [x,y,z];
}
// EXAMPLE: For the equations:
// 2X + 3Y + 4Z = 119
// 5X - 6Y + 7Z = 80
// 8X + 9Y + 10Z = 353
// X=12, Y=13, Z=14
// var tmp = three_unknowns( 2, 3, 4, 119, 5, -6, 7, 80, 8, 9, 10, 353 );
// if (repr(tmp) != repr([12,13,14])) {
// my_alert("ERR: three_unknowns() returned " + repr(tmp) + " instead of [12,13,14]");
// } else {
// console_log( "three_unknowns() returned " + repr(tmp) + " = [12,13,14], which is correct." );
// }
// Least-squares fit of a quadratic curve to a set of weighted points.
// Input is two or three arrays of identical length: x_vals (the X-coordinates),
// y_vals (the Y-coordinates), and optionally weights (the weights).
// Result is the B, M & A coefficients of the Least-Squares best-fit quadratic,
// Y = B + M*X + A*(X**2)
// A is half the "acceleration."
// You can omit the 3rd parameter if all weights are equal.
function basic_quadratic_regression( x_vals, y_vals, weights ) {
var x, y, w, N, a1, b1, c1, d1, c2, d2, d3, sumw, A, B, C, i;
N = x_vals.length;
if (N != y_vals.length) {
throw new Error("ERR: weighted_quadratic_fit, first two parameters are not arrays of same length.");
}
if (!N) {
result = [0,0,0];
} else if (1==N) {
result = [y_vals[0], 0, 0];
} else if (2==N) {
result = basic_linear_regression(values_x, values_y);
result.push(0);
} else {
if (undefined == weights) {
weights = [];
for (i=0; i max_x)) {
result = avg_x;
}
}
return result;
}
// Select & extract a copy of one column...
// Input is:
// 1. an integer column number (subscript), in the range 0..N-1; and,
// 2. an MxN two-dimensional array (array of arrays), i.e., an M-row array of column arrays, each of length N.
// Output is:
// A vector (one-dimensional array) containing a copy of the contents of the selected column.
function column( column_index, rows ) {
var result = [];
if (isArray(rows) && isArray(rows[0])) {
var n_rows = rows.length;
// console_log('column(' + column_index + ', rows)');
if (n_rows) {
var n_cols = rows[0].length;
var i, tmp;
if (column_index >= n_cols) {
throw new Error('ERR: column(' + column_index + ', rows) with only ' + n_colus + ' columns.');
}
for (i=0; i maxval) {
result = maxval;
}
return result;
}
// window.location.search will return everything from the ? on. The following code
// removes the ?, uses split to separate into key/value arrays, then assigns named
// properties to the params object, which is returned as the funtion result.
// The result would be more naturally represented as an associative array (which
// javascript stupidly calls a Map, even though "map" means something entirely
// unrelated in javascript), but, unbelievably, as of 2016, some major javascript
// implementations stupidly still don't support Maps. Stupid javascript!
// Based on:
// http://stackoverflow.com/questions/5448545/how-to-retrieve-get-parameters-from-javascript/5448635#5448635
// In node.js it will, instead, get space-separated command-line parameters
function get_Get_parameters_from_URI() {
var i, params = {}; // result
var prmarr;
if (ThisIsNode) {
// console.log('dbg: process.argv = ' + repr(process.argv));
prmarr = process.argv.slice(2);
} else {
// we must be in a browser
var prmstr = window.location.search.substr(1); // http://www.w3schools.com/jsref/prop_loc_search.asp
if (prmstr) {
prmarr = prmstr.split("&");
}
}
// console.log('dbg: prmarr = ' + repr(prmarr));
if (prmarr) {
for (i=0; i < prmarr.length; i++) {
var tmparr = prmarr[i].split("=");
var key = tmparr[0];
if (tmparr.length < 2) {
// specifying a parameter w/o "=..." is like specifying it with "=1"
if (!params.hasOwnProperty(key)) {
params[key] = 1;
} else {
params[key] += 1; // specifying it twice is like specifying it with "=2"
}
} else {
params[key] = tmparr[1];
}
}
}
return params;
}
var params = get_Get_parameters_from_URI();
// Adjust URL in browser address bar by updating the parameters after the "?".
//
// new_param is the new parameter to be inserted in the URL (if it has a leading
// "&" that's okay, but it is not required, and a leading "?" is NOT okay).
//
// regex_for_param is a regular expression which matches the old parameters that
// need to be replaced, including a leading "&". Note that we fake-up a leading
// "&" for the first parameter (instead of "?"), to simplify regex_for_param.
//
// If you pass true for the optional 3rd parameter, then this function uses
// history.pushState() rather than history.replaceState(), so the Back button
// will work in the browser.
//
// URL / URI syntax: https://sealevel.info/URI_syntax_diagram.png
// Simplified:
// {scheme} "://" {host} [ "/" {path} ] [ "?" {query} ] [ "#" {fragment} ]
//
// Example:
// fixup_url( /\&co2((\=[^\&]*)|)/ig, 'co2=' + showCO2 );
function fixup_url( regex_for_param, new_param, push_rather_than_replace ) {
var href = window.location + ''; // current URL
var fragment = '';
var query = '';
var path = '';
var tmp = href.split('#');
if (tmp.length > 0) {
path = tmp[0];
if (tmp.length > 1) {
fragment = tmp[1];
}
tmp = path.split('?');
path = tmp[0];
if (tmp.length > 1) {
query = tmp[1];
}
}
var optns = query;
if (tmp.length > 1) {
optns = '&' + tmp[1];
}
var oldoptns = optns;
console_log('fixup_url(' + repr(regex_for_param) + ', ' + repr(new_param) + ', ' + push_rather_than_replace + ')');
console_log('window.location = "' + href + '"');
optns = optns.replace(regex_for_param,'');
console_log('old optns=' + oldoptns);
if (new_param) {
if ('&' != new_param.substr(0,1)) {
optns += '&';
}
optns += new_param;
}
console_log('new optns=' + optns);
if (optns !== oldoptns) {
// update URL in browser address bar
optns = optns.substring(1);
if ('' === optns) {
href = tmp[0];
} else {
href = tmp[0] + '?' + optns;
}
if (fragment.length > 0) {
href = href + '#' + fragment;
}
console_log('pushstate(...,"' + href + '")');
try {
if (push_rather_than_replace) {
history.pushState('', '', href);
} else {
history.replaceState('', '', href);
}
}catch(ex){;} // might be an error in some old browsers
}
}
// Subroutine of boxcar_smoothing():
// Retrieve a single "y value" (MSL) from y_vals[i].
// If the value is unavailable (null), or the index is out of range, return null.
// Single-month gaps are filled-in by interpolation; multi-month gaps are not. I justify
// that because Zervas 2009 found a lot of correlation between adjacent months, but not
// much between months further apart.
function boxcar_fetch_y(y_vals, i) {
var result = null;
if ((i >= 0) && (i < y_vals.length)) {
result = y_vals[i];
if (result === null) {
// missing month: interpolate from adjacent months, if they're both available
if ((i > 0) && (i < (y_vals.length-1))) {
var y1 = y_vals[i-1];
var y2 = y_vals[i+1];
if ((y1 !== null) && (y2 !== null)) {
result = (y1 + y2) / 2;
}
}
}
}
return result;
}
// Subroutine of boxcar_smoothing():
// "Boxcar" smoothing of just one data point.
// If len is odd, then we do a pure boxcar, and smooth over (len-1)/2 months before and after each month.
// If len is even, then we do a modified boxcar: we smooth over len/2 months before and after each month,
// but the 1st & last samples are weighted half.
function boxcar_one_point(y_vals, i,len) {
var result = null;
var len1 = Math.floor(len/2);
var i1 = i-len1, i2 = i+len1;
var j, y, vals = [];
for (j=i1; j<=i2; j++) {
y = boxcar_fetch_y(y_vals,j);
if (null==y) { // tests for null or undefined
return null;
}
vals.push(y);
}
if (isEven(len)) {
vals[0] = (vals[0]+vals.pop())/2;
}
result = avg(vals);
// console_log('boxcar_one_point(' + i + ',' + len + ') = ' + repr(result.toFixed(3)) + ' (y=' + repr(values_full_y[i]) + ')');
return result;
}
// "Boxcar" smoothing.
// If len is odd, then we do a pure boxcar, and smooth over (len-1)/2 months before and after each month.
// If len is even, then we do a modified boxcar: we smooth over len/2 months before and after each month,
// but the 1st & last samples are weighted half.
// For one-year smoothing use len=12, for two-year use len=24, etc.
// For one pass, set rpt=1. For repeated boxcar smoothing set rpt >= 2.
function boxcar_smoothing(input_y_vals, len, rpt) {
var i, result;
// console_log('boxcar_smoothing( ..., ' + len + ', ' + (rpt-1) + ')');
if (rpt <= 0) {
result = input_y_vals.slice();
} else {
result = [];
for (i=0; i 1) {
// console_log(' recursive boxcar_smoothing( ..., ' + len + ', ' + (rpt-1) + ')');
result = boxcar_smoothing(result, len, rpt-1);
}
}
return result;
// yes, I know this code is not very efficient, but it is fast enough.
}
// rows is an array of rows, each of which consists of an array of columns.
// I.e., rows is a rectangular 2D array. (Each row must be the same width.)
// Input array is not modified.
// Result is a new 2D array, with the same number of rows, but total_num_cols wide.
// Each output row nas num_inserted_cols nulls inserted between col#0 and col#1 of the
// corresponding input row. Additionally, if needed, nulls are appended, to extend
// the row to total_num_cols columns.
function pad_rows_with_nulls(rows, num_inserted_cols, total_num_cols) {
var result = [];
if (rows.length > 0) {
var old_num_cols = rows[0].length;
var num_appended_cols = total_num_cols - (old_num_cols+num_inserted_cols);
// Note: if old_num_cols==total_num_cols then this function is a complicated NOP
// var nulls1 = new Array(num_inserted_cols).fill(null); -- stupid IE11 doesn't support .fill(), though Edge does
var nulls1 = []; for (var i=0; i yr)) {
i2 = i;
}
}
}
// if the desired date is in the range of dates for which we have CO2 data, then i1 and i2 are the bracketing subscripts
console_log('interploate_CO2 for ' + yr + '/' + mo + ' = ' + dd.toFixed(2) + ', between i1=' + i1 + ' and i2=' + i2 );
if ((null !== i1) && (null !== i2)) {
console_log('interploate_CO2 for ' + yr + '/' + mo + ' = ' + dd.toFixed(2) + ', between [' + i1 + '](' + co2_and_ch4[i1][0] + ') and [' + i2 + '](' + co2_and_ch4[i2][0] + ')');
var v1, v2;
if (co2_and_ch4[i1][2]) {
v1 = co2_and_ch4[i1][2]; // Mauna Loa
} else {
v1 = co2_and_ch4[i1][1];
from_icecore = true;
}
if (co2_and_ch4[i2][2]) {
v2 = co2_and_ch4[i2][2]; // Mauna Loa
} else {
v2 = co2_and_ch4[i2][1];
from_icecore = true;
}
// calculate the interpolated value
var d1 = co2_and_ch4[i1][0];
if (isInteger(d1)) {d1 += 0.5;} // annual values are roughly equivalent to mid-year values
var d2 = co2_and_ch4[i2][0];
if (isInteger(d2)) {d2 += 0.5;}
var result1 = v1 + (((dd-d1)/(d2-d1)) * (v2-v1)); // = v1 * (d2-dd)/(d2-d1) + v2 * (dd-d1)/(d2-d1) = ((v1 * (d2-dd)) + (v2 * (dd-d1))) / (d2-d1);
if (from_icecore) {
result = [result1, null];
} else {
result = [null, result1];
}
if ((dd < 1958.5) != from_icecore) {
my_alert('ERR: decimal date = ' + dd.toFixed(4) + ' but from_icecore=' + from_icecore);
}
}
return result; // if we couldn't interpolate because desired date was out of range, this is null
}
// Traces object's constructor, creates a new Traces object to be added to the global
// traces[] array (but this doesn't add it). A traces object contains the things that
// a graph trace requires:
// 1. 2D array, w/ dates in column 0, and one or more data values in subsequent column(s),
// to be passed to data.addRows()
// 2. Column names, an array of one or more elements, to be passed to data.addColumn()
// 3. Colors, an array of one or more elements, for options.colors[]
// 4. options.series values, an array of one or more object elements like {targetAxisIndex:1}
// or {targetAxisIndex:0, visibleInLegend:false}, for options.series[]
function Traces( trace_data_array, trace_names, trace_colors, trace_options ) {
if (!(this instanceof Traces)) {
// per http://stackoverflow.com/questions/383402/is-javascripts-new-keyword-considered-harmful/383503#383503
throw new Error("ERR: Trace() onstructor called as a function, instead of with the 'new' keyword.");
}
var i, num_names = trace_names.length, num_colors = trace_colors.length, num_options = trace_options.length;
var num_traces = max([num_names, num_colors, num_options]);
if (trace_data_array.length > 0) {
num_traces = trace_data_array[0].length - 1;
}
if ((num_names != num_traces) || (num_colors != num_traces) || (num_options != num_traces)) {
throw new Error("ERR: Trace() constructor parameter error, for " + num_traces + " graph traces; names/colors/options array lengths should all be " + num_traces + ", but are " + num_names + ", " + num_colors + " & " + num_options + ", respectively.")
}
this.num_traces = num_traces;
this.rows = trace_data_array;
this.names = trace_names;
this.colors = trace_colors;
this.series = trace_options;
console_log('new Traces() object created, for ' + num_traces + ' graph traces:');
for (i=0; i oldest_yr) || ((year == oldest_yr) && (mo >= oldest_mo))) {
var i = ((year-oldest_yr)*12) + (mo-oldest_mo);
if (i < stdt.msl_data.length) {
result = stdt.msl_data[i][7];
if (-99999 == result) {
result = null;
}
}
}
return result;
}
var dbg3_kludge_cnt = 0;
// Convert PSMSL's reported RLR value (in mm) to NOAA equivalent (in meters).
// mo is the month, 1-12, needed for looking up the seasonal cycle adjustment.
// Two globals are required:
// stdt.seasonal_cycle[] is used to look up the seasonal cycle adjustment.
// stdt.psmsl_to_noaa_offset is used for the baseline adjustment.
function psmsl_rlr_to_noaa( stdt, mo, rlr_mm ) {
if (dbg3_kludge_cnt < 3) {
console_log("dbg3_kludge_cnt=" + dbg3_kludge_cnt + ", (seasonal_cycle in stdt)=" + ('seasonal_cycle' in stdt) + ", (noaa_seasonal_cycle in stdt)=" + ('noaa_seasonal_cycle' in stdt) + ", (seasonal_cycle_calculated in stdt)=" + ('seasonal_cycle_calculated' in stdt));
dbg3_kludge_cnt++;
}
if (!('seasonal_cycle' in stdt)) {
if ('noaa_seasonal_cycle' in stdt) {
stdt.seasonal_cycle = stdt.noaa_seasonal_cycle;
console_log("dbg3: using noaa_seasonal_cycle\n")
} else {
if (!('seasonal_cycle_calculated' in stdt)) {
stdt.seasonal_cycle_calculated = calculate_seasonal_cycle_from_psmsl(stdt);
console_log("dbg3: calculating seasonal_cycle_calculated\n")
}
stdt.seasonal_cycle = stdt.seasonal_cycle_calculated;
console_log("dbg3: using seasonal_cycle_calculated\n")
}
}
if (12 != stdt.seasonal_cycle.length) {console.log("dbg3: stdt.seasonal_cycle.length=" + stdt.seasonal_cycle.length + "\n");}
var adj = stdt.seasonal_cycle[mo-1]; // adjustment for seasonal cycle, as determined by NOAA
// var adj = stdt.seasonal_cycle_calculated[mo-1]; // adjustment for seasonal cycle, as determined by my calculation
var result;
if ((null == rlr_mm) || (-99999 == rlr_mm)) {
result = null;
} else {
result = rlr_mm;
if ('psmsl_to_noaa_offset' in stdt) {
result -= stdt.psmsl_to_noaa_offset; // stdt.psmsl_to_noaa_offset is from psmsl_noaa_map_adj.csv, or calculated from data
}
result /= 1000; // convert from mm to meters
if ((adj != null) && !isNaN(adj)) {
result -= adj; // PSMSL doesn't adjust for seasonal cycle, and NOAA does
}
}
return result;
}
// measurement deviation from the average of the preceeding 5.5 months and subsequent 5.5 months of PSMSL data
// mo is 1..12
// returns null if it can't be determined
function estimate_mo_seas_cyc_from_one_sample( stdt, yr, mo ) {
// var dbgmode = (1936==yr) && (6==mo);
var result = null;
var s13 = []; // thirteen samples
var mo1, yr1;
yr1 = yr;
mo1 = mo - 6;
if (mo1 <= 0) {
mo1 += 12;
yr1--;
}
for (var i=0; i<13; i++) {
var msl = psmsl_msl(stdt,yr1,mo1);
// if (dbgmode) { console_log('dbg1: ' + yr1 + '/' + mo1 + ': msl=' + msl); }
if (null == msl) { // this checks for null or undefined
return null; // if any of the 12 surrounding values are unavailable, give up and return null
}
s13.push(msl);
mo1++;
if (13 == mo1) {
mo1 = 1;
yr1++;
}
}
// Calculate average of the 13 months, except first & last month in the series are
// weighted half (because they're the same month, but one year apart). E.g., if
// mo=6 (June) then s13[0] and s13[12] are both December, and we don't want to give
// double weight to the month of December so we give half weight to each of the two
// Decembers.
var avg = (s13[0]+s13[12])/2;
for (var i=1; i<12; i++) {
avg += s13[i];
}
avg /= 12;
result = s13[6] - avg;
if (s13[6] != psmsl_msl(stdt,yr,mo)) {
my_alert('ERR: s13[6]=' + s13[6] + ' != psmsl_msl(stdt,yr,mo)' + '=' + psmsl_msl(stdt,yr,mo));
}
// if (dbgmode) {
// console_log('dbg4: ' + '1936-6: s13=' + s13.join(',')+ ' avg=' + avg + ', adj1=' + result);
// }
return result;
}
// calculate seasonal cycle adjustment for a specified month, 1..12, from PSMSL data
function calc_seasonal_cycle_for_one_month(stdt, mo) {
var result = 0;
var sum_sq_deviations = 0;
var seasonal_offset;
var sd = 0;
var first_yr = stdt.msl_data[0][0];
var offsets = [];
for (var yr=first_yr; yr<=this_yr; yr++) {
seasonal_offset = estimate_mo_seas_cyc_from_one_sample( stdt, yr, mo );
if (null != seasonal_offset) {
offsets.push(seasonal_offset);
}
}
if (offsets.length) {
result = avg(offsets);
} else {
result = 0;
}
// console_log('month=' + mo + ', avg=' + result + ' mm');
result /= 1000; // convert from mm to meters
// if (offsets.length > 1) {
// var deviations_from_seasonal_avg = deviations(offsets);
// sum_sq_deviations = sum_of_squares(deviations_from_seasonal_avg);
// sd = Math.sqrt( sum_sq_deviations / (offsets.length-1) );
// }
// console_log('month=' + mo + ', avg=' + result + ' meters, count=' + deviations.length + ', SD=' + sd + ' mm');
// console_log('offsets = [' + offsets.join(',') + ']');
// console_log('deviations = [' + deviations_from_seasonal_avg.join(',') + ']');
// Hmmm... the SD that I calculate is about 6 times NOAA's, and I have no idea why
// Well, I don't use it for anything, anyhow, but it still bothers me.
// sd /= 1000;
return result;
}
// Calculate average seasonal cycles from PSMSL data
function calculate_seasonal_cycle_from_psmsl(stdt) {
var seasonal_cycle_calculated = [];
for (var i=1; i<=12; i++) {
var calc_adj = calc_seasonal_cycle_for_one_month(stdt,i);
// var noaa_adj = stdt.seasonal_cycle[i-1];
// console_log('seasonal adj for month ' + i + ': calc=' + calc_adj.toFixed(3) + " NOAA=" + noaa_adj);
// Mine are mostly within a couple of mm of NOAA's.
// The largest difference is for September, when I calculate 80 mm and NOAA says 84.
seasonal_cycle_calculated.push(calc_adj);
}
return seasonal_cycle_calculated;
}
// Return true if two numbers are nearly identical (i.e., if they're so close that
// the difference could be due to arithmetic imprecision).
// If you pass arrays instead of numbers, then it returns true if and only if the
// arrays are the same length and all of the elements are nearly identical.
// The optional 3rd parameter should be very slightly larger than one. It defaults
// to 1.00000001. It's the "multiplicative precision." If you want a more stringent
// result, increase the number of zeros.
// The optional 4th parameter should be a very small number. It is the "additive
// precision," i.e., the +/- value used for comparing numbers which are close to zero.
// It defaults to 0.00000000001. It is used if the multiplicative test fails because
// the numbers are very small (or if they are of differing sign). If you want a more
// stringent result, increase the number of zeros.
function nearly_identical( a, b, mul_precision, add_precision ) {
var result = true;
if (isArray(a)) {
if (!isArray(b)) {
result = false;
} else {
if (a.length != b.length) {
result = false;
} else {
for (var i=0; i 0) {
// a and b are of the same sign
if (null == mul_precision) {
msg1 += 'mul_precision=' + repr(mul_precision) + '\n';
mul_precision = 1.00000001;
}
result = ((a * mul_precision) > b) == ((a * (2-mul_precision)) < b);
if (!result) {
msg1 += 'mul_precision=' + mul_precision + ' \na=' + a + ' \nb=' + b + '\n(mul test failed)\n';
}
}
if (!result) {
// special-case kludge for tiny numbers, possibly of opposite sign, or when one of them is zero
if (null == add_precision) {
msg1 += 'add_precision=' + repr(add_precision) + '\n';
add_precision = 0.00000000001;
}
result = Math.abs(a-b) < add_precision;
msg1 += 'add_precision=' + add_precision + ' \nresult=' + result + ' \na=' + a + ' \nb=' + b + ' \ndif=' + Math.abs(a-b) + '\n';
if (!result) {
msg1 += '(add test failed)\n';
}
}
}
if (!result) {
console.log('\n' + msg1);
warn_once('\n' + msg1, 'nearly_identical');
}
}
return result;
}
// a linear regression trendline or a quadratic regression curve. Returns vector of residuals.
// The last input parameter ('a', the quadratic coefficient) is optional, and defaults to zero.
function residuals( Xs, Ys, b, m, a ) {
var result = vect_sub( Ys, Ys_from_Xs(Xs,b,m,a) );
return result;
}
// Synchronously load a .json file from the web server with Ajax
// See the answer by "avok00" at
// http://stackoverflow.com/questions/4116992/how-to-include-json-data-in-javascript-synchronously-without-parsing
// and also https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
//
// The filePath is relative to the main web page folder (or perhaps to sealevelcalc.js's folder?).
// Example: mydict = loadJSON("Chuuk_data.json");
//
// You can also pass a full URL, like http://sealevel.info/Chuuk1_data.json, and that works okay in
// Firefox, IE, Edge or Opera, but in Chrome it only works if the domain exactly matches the web page
// domain (and note that "sealevel.info" & "www.sealevel.info" don't match). Otherwise it gets an error:
//
// No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://sealevel.info' is therefore not allowed access.
//
// That happens even though I configured .htaccess and verified that "Access-Control-Allow-Origin *"
// is included in the headers returned (use http://www.webconfs.com/http-header-check.php to check it).
// I think it's a Chrome bug.
function loadJSON(filePath) {
var result = null;
if (ThisIsNode) {
console_log('dbg: loadJSON(' + filePath + ', ThisIsNode=true'); // I was using `back-ticks`, but IE 11 doesn't like them
var fs = require('fs'); // node.js file I/O module
try {
// result = fs.readFileSync(filePath);
result = fs.readFileSync(filePath,{encoding:'utf8'}); // specify encoding to get it back as a string
} catch (er) {
console_log(er);
result = '';
}
} else {
// running in a browser it's a bit trickier
var xmlhttp = new XMLHttpRequest();
if (xmlhttp.overrideMimeType) {
// This isn't really necessary, and xmlhttp.overrideMimeType("text/plain") also seems to work okay
xmlhttp.overrideMimeType("application/json");
}
xmlhttp.open("GET", filePath, false);
// xmlhttp.setRequestHeader("Authorization", "Negotiate") -- This was an attempt to make it work cross-site in Chrome; no joy
xmlhttp.send();
if (xmlhttp.status==200) {
result = xmlhttp.responseText;
}
}
result = JSON.parse(result);
return result;
}
// Node.js doesn't let a program set a module's globals directly, so we need helper functions:
// set to "rebase year" (exact value doesn't matter too much, but should be before first data point)
function date_rebase_yr_setter(yr) {
date_rebase_yr = yr;
}
// set true to quiet down sealevelcalc.js
function suppress_console_log_setter(bool) {
suppress_console_log = bool;
}
// This isn't actually needed when importing this module via
// into HTML & PHP pages, but it is needed when importing this module via require('./sealevelcalc.js'); in node.js.
// A few of the functions aren't used (or usable) from node.js, but I list all exterally used functions, anyhow, for consistency.
if (ThisIsNode) {
// this is a Node.js program
module.exports = {
// Note: I use the long form ("abc:abc") only because the short form ("abc") breaks IE 11.
// Functions defined:
isArray:isArray, isNumber:isNumber, isInteger:isInteger, isEven:isEven,
isString:isString, repr:repr, deepcopy:deepcopy, min:min, max:max, avg:avg,
vect_add:vect_add, vect_sub:vect_sub, vect_mult:vect_mult, vect_summation:vect_summation,
sum_of_squares:sum_of_squares, sum_in_quadrature:sum_in_quadrature,
deviations:deviations, decimal_date:decimal_date,
year_mo_from_decimal_date:year_mo_from_decimal_date,
dd2yr:dd2yr, dd2mo:dd2mo, dd_repr:dd_repr,
ratio_of_std_errs_from_AR1:ratio_of_std_errs_from_AR1, y_from_x:y_from_x,
Ys_from_Xs:Ys_from_Xs, basic_linear_regression:basic_linear_regression,
linear_regression:linear_regression, three_unknowns:three_unknowns,
basic_quadratic_regression:basic_quadratic_regression,
find_date_where_quadratic_x_coefficient_matches_linear_slope:find_date_where_quadratic_x_coefficient_matches_linear_slope,
column:column, columns:columns, swap_rows_and_columns:swap_rows_and_columns,
add_columns:add_columns, warn_once:warn_once, this_year:this_year,
eval_int:eval_int, get_Get_parameters_from_URI:get_Get_parameters_from_URI,
fixup_url:fixup_url, boxcar_smoothing:boxcar_smoothing,
interpolate_CO2:interpolate_CO2, psmsl_msl:psmsl_msl, psmsl_rlr_to_noaa:psmsl_rlr_to_noaa,
calculate_seasonal_cycle_from_psmsl:calculate_seasonal_cycle_from_psmsl,
nearly_identical:nearly_identical, residuals:residuals, loadJSON:loadJSON,
console_log:console_log, date_rebase_yr_setter:date_rebase_yr_setter,
suppress_console_log_setter:suppress_console_log_setter, my_alert:my_alert,
// Object constructor defined:
Traces:Traces,
// Other globals defined:
globalThis:globalThis, map_supported:map_supported, set_supported:set_supported,
weakmap_supported:weakmap_supported, weakset_supported:weakset_supported,
suppress_warnings:suppress_warnings, suppress_console_log:suppress_console_log,
co2_and_ch4:co2_and_ch4, params:params, date_rebase_yr:date_rebase_yr
};
}
]