This is the Javascript source code of the online calculator in the article Measuring Earths Radius like Al-Biruni taking Refraction into account.
#INCLUDE ControlPanel.inc <jscript> function ComputeMeanAndTolerance( func, means, tolerances ) { // uses the means[n] and tolerances[n] to generate all possible // variation[i] = means[i] +/- tolerances[i] and calls func(variation[i]) // for each variation. From the results of func() the min and max // values are determined. // // Returns { exact:, mean:, tolerance: } value from all variantions, // where mean = (max + min) / 2 and tolerance = (max - min) / 2. function getNMaskValues( nParam ) { return Math.pow( 3, nParam ); } function makeVariation( mask, means, tolerances ) { // Uses mask to add or subtract tolerance[i] from means[i] // to get all possible variations[n]. Increasing the mask // and call this function repeatedly to get all possible variations. // mask = 0 returns means[] without tolerances[] applied. // Call getNMaskValues() to calculate the max value+1 for mask. // // mask = 0 -> [ m[0] , m[1] , ..., m[n] ] // mask = 1 -> [ m[0]-t[0], m[1] , ..., m[n] ] // mask = 2 -> [ m[0]+t[0], m[1] , ..., m[n] ] // mask = 3 -> [ m[0] , m[1]-t[1], ..., m[n] ] // mask = 4 -> [ m[0]-t[0], m[1]-t[1], ..., m[n] ] // mask = 5 -> [ m[0]+t[0], m[1]-t[1], ..., m[n] ] // mask = 6 -> [ m[0] , m[1]+t[1], ..., m[n] ] // mask = 7 -> [ m[0]-t[0], m[1]+t[1], ..., m[n] ] // mask = 8 -> [ m[0]+t[0], m[1]+t[1], ..., m[n] ] // and so on var variations = []; var nValues = means.length; for (var i = 0; i < nValues; i++) { variations[i] = means[i]; } for (var i = 0; i < nValues; i++) { var maskBit = mask % 3; if (maskBit === 1) { variations[i] -= tolerances[i]; } else if (maskBit === 2) { variations[i] += tolerances[i]; } mask = Math.trunc( mask/3 ); if (mask === 0) break; } return variations; } var exactValue = func( ...means ); var minValue = exactValue; var maxValue = exactValue; var nMaskValues = getNMaskValues( means.length ); for (var mask = 1; mask < nMaskValues; mask++) { var pars = makeVariation( mask, means, tolerances ); var currentValue = func( ...pars ); if (currentValue < minValue) minValue = currentValue; if (currentValue > maxValue) maxValue = currentValue; } var mean = (minValue + maxValue) / 2; var tolerance = (maxValue - minValue) / 2; return { exact: exactValue, mean: mean, tolerance: tolerance }; } var AlBiruniModel = { Pressure: 1013.25, // mbar PressureAbsTol: 1, // mbar PressureRelTol: 0, // AbsTol/Mean in % TempC: 15.5, TempCAbsTol: 0.25, TempCRelTol: 0, TempGrad: -0.01221, // °C/m TempGradAbsTol: 0.0005, TempGradRelTol: 0, RefrCoeff: 0.17, RefrCoeffAbsTol: 0.04, RefrCoeffRelTol: 0, LightCurve: 0, LightCurveAbsTol: 0, LightCurveRelTol: 0, LightRad: 0, LightRadAbsTol: 0, LightRadRelTol: 0, ObsHeight: 358, // m ObsHeightAbsTol: 12, // m ObsHeightRelTol: 0, DipAngle: 0.56667, // deg DipAngleAbsTol: 0.016667, DipAngleRelTol: 0, R: 0, // m RMean: 0, // m RAbsTol: 0, // m RRelTol: 0, DistAB: 1000, DistABAbsTol: 5, DistABRelTol: 0, Alpha: 45, // degrees AlphaAbsTol: 0.25, AlphaRelTol: 0, Beta: 14.75, BetaAbsTol: 0.25, BetaRelTol: 0, FeMntHeight: 0, FeMntHeightMean: 0, FeMntHeightAbsTol: 0, FeMntHeightRelTol: 0, GlobeMntHeight: 0, GlobeMntHeightMean: 0, GlobeMntHeightAbsTol: 0, GlobeMntHeightRelTol: 0, MntRefrCoeff: 0.13, MntRefrCoeffAbsTol: 0.026, MntRefrCoeffRelTol: 0, GlobeFlatDiff: 0, GlobeFlatDiffMean: 0, GlobeFlatDiffAbsTol: 0, GlobeFlatDiffrelTol: 0, UnitsType: 0, // 0 -> m, 1 -> mi/ft, 2 -> ft LengthUnits: { Selection: '.UnitsType', Units: [ 'm', 'mi', 'ft' ], Mults: [ 1, 1609.344, 0.3048 ] }, BigLengthUnits: { Selection: '.UnitsType', Units: [ 'km', 'mi', 'ft' ], Mults: [ 1000, 1609.344, 0.3048 ] }, InvLengthUnits: { Selection: '.UnitsType', Units: [ 'm<sup>-1</sup>', 'mi<sup>-1</sup>', 'ft<sup>-1</sup>' ], Mults: [ 1, 1/1609.344, 1/0.3048 ], Digits:[ 6, 6, 6 ], }, HeightUnits: { Selection: '.UnitsType', Units: [ 'm', 'ft', 'ft' ], Mults: [ 1, 0.3048, 0.3048 ] }, GradientUnits: { Selection: '.UnitsType', Units: [ '°C/m', '°C/ft', '°C/ft' ], Mults: [ 1, 1/0.3048, 1/0.3048 ] }, AngleFormat: 1, // 0 -> deg, 1 -> DM, 2 -> DMS AngleFormats: { Selection: '.AngleFormat', Units: [ '°', '', '' ], Formats: [ 'prec', 'dm', 'dms' ], }, Comp_RefrCoeff_from_LightCurve: function( lightCurve ) { return lightCurve * 6371000; }, Comp_LightCurve_from_RefrCoeff: function( refrCoeff ) { return refrCoeff / 6371000; }, Comp_LightCurve_from_Pressure_TempC_TempGrad: function( pressure, tempC, tempGrad ) { return 7.895e-5 * pressure / Math.pow( tempC + 273.15, 2 ) * (0.0343 + tempGrad); }, Comp_TempGrad_from_LightCurve_Pressure_TempC: function( lightCurve, pressure, tempC ) { var tempAbs = tempC + 273.15; return (lightCurve * tempAbs * tempAbs) / (7.895e-5 * pressure) - 0.0343; }, Comp_LightRad_from_LightCurve: function( lightCurve ) { return 1 / lightCurve; }, Comp_LightCurve_from_LightRad: function( lightRad ) { return 1 / lightRad; }, Comp_Pressure_from_ObsHeight: function( obsHeight ) { var g = 9.80665; var Rs = 287.058; var alpha = -0.0065; var Tref = 288.15; var Pref = 1013.25; var beta = g / Rs / alpha; var p = Pref * Math.pow( (Tref + alpha * obsHeight) / Tref, -beta ); return p; }, Comp_R_from_ObsHeight_DipAngle_LightCurve: function( obsHeight, dipAngleDeg, lightCurve ) { var cosDip = Math.cos( dipAngleDeg * Math.PI / 180 ); return (obsHeight* cosDip - 0.5 * lightCurve * obsHeight * obsHeight) / (lightCurve * obsHeight + 1 - cosDip); }, Comp_FeMntHeight_from_Alpha_Beta_DistAB: function( alpha, beta, distAB ) { var tanAlpha = Math.tan( alpha * Math.PI / 180 ); var tanBeta = Math.tan( beta * Math.PI / 180 ); var h = distAB * tanAlpha * tanBeta / (tanAlpha - tanBeta); return h; }, Comp_GlobeMntHeight_from_Alpha_Beta_DistAB_MntRefrCoeff: function( alpha, beta, distAB, refrCoeff ) { // note: using the refracted curvature of earth CRrefr rather than the refracted radius of curvature R' // prevents divisions by zero for k = 1 var R = 6371000; var CRrefr = (1 - refrCoeff) / R; var tanAlpha = Math.tan( alpha * Math.PI / 180 ); var tanBeta = Math.tan( beta * Math.PI / 180 ); var x = distAB * (tanBeta + 0.5 * distAB * CRrefr) / (tanAlpha - tanBeta - distAB * CRrefr); var h = x * tanAlpha + x*x * CRrefr / 2; return h; }, Comp: function( resultName, argNames ) { var funcName = 'Comp_' + resultName + '_from'; var means = []; var tols = []; var nArgs = argNames.length; for (var i = 0; i < nArgs; i++) { funcName += '_' + argNames[i]; means[i] = this[argNames[i]]; tols[i] = this[argNames[i]+'AbsTol']; } var result = ComputeMeanAndTolerance( this[funcName], means, tols ); this[resultName] = result.exact; this[resultName+'AbsTol'] = result.tolerance; this[resultName+'RelTol'] = 100 * Math.abs( result.tolerance / this[resultName] ); return result.mean; }, Update: function( field ) { function allNames( namesStr ) { // returns expanded namesStr to include all names + their tolerances // e.g. namesStr = 'DipAngle' -> 'DipAngle,DipAngleAbsTol,DipAngleRelTol' var names = namesStr.split( ',' ); var all = []; for (var i = 0; i < names.length; i++) { all.push( names[i] ); all.push( names[i]+'AbsTol' ); all.push( names[i]+'RelTol' ); } return all.join( ',' ); } var fieldNames = [ 'Pressure' , 'TempC' , 'TempGrad' , 'LightCurve', 'LightRad', 'RefrCoeff', 'ObsHeight' , 'DipAngle', 'MntRefrCoeff', 'Alpha' , 'Beta' , 'DistAB' ]; // convert relative tolerance entries to absolute tolerances if (xDef(field)) { for (var i = 0; i < fieldNames.length; i++) { if (ControlPanels.MatchesField( field, fieldNames[i]+'RelTol' )) { this[fieldNames[i]+'AbsTol'] = this[fieldNames[i]+'RelTol'] * Math.abs(this[fieldNames[i]]) / 100; } } } // if observer height is changed, calculate and set the pressure according the international standard atmosphere // the tolerances are kept untouched if (ControlPanels.MatchesField( field, 'ObsHeight' )) { this.Pressure = this.Comp_Pressure_from_ObsHeight( this.ObsHeight ); } // when refraction coeff, light curvature or light radius is changes, compute temperature gradient from it // keeping pressure and absolute temperature constant if (xDef(field) && ControlPanels.MatchesField( field, allNames('RefrCoeff,LightCurve,LightRad') )) { if (ControlPanels.MatchesField( field, allNames('LightRad') )) { this.Comp( 'LightCurve', [ 'LightRad' ] ); this.Comp( 'RefrCoeff', [ 'LightCurve' ] ); } if (ControlPanels.MatchesField( field, allNames('LightCurve') )) { this.Comp( 'RefrCoeff', [ 'LightCurve' ] ); } if (ControlPanels.MatchesField( field, allNames('RefrCoeff') )) { this.Comp( 'LightCurve', [ 'RefrCoeff' ] ); } this.Comp( 'TempGrad', [ 'LightCurve', 'Pressure', 'TempC' ] ); } else { this.Comp( 'LightCurve', [ 'Pressure', 'TempC', 'TempGrad' ] ); this.Comp( 'RefrCoeff', [ 'LightCurve' ] ); } this.Comp( 'LightRad', [ 'LightCurve' ] ); this.RMean = this.Comp( 'R', [ 'ObsHeight', 'DipAngle', 'LightCurve' ] ); this.FeMntHeightMean = this.Comp( 'FeMntHeight', [ 'Alpha', 'Beta', 'DistAB' ] ); this.GlobeMntHeightMean = this.Comp( 'GlobeMntHeight', [ 'Alpha', 'Beta', 'DistAB', 'MntRefrCoeff' ] ); this.GlobeFlatDiff = this.GlobeMntHeight - this.FeMntHeight; this.GlobeFlatDiffMean = this.GlobeMntHeightMean - this.FeMntHeightMean; this.GlobeFlatDiffAbsTol = this.GlobeMntHeightAbsTol - this.FeMntHeightAbsTol; this.GlobeFlatDiffRelTol = this.GlobeMntHeightRelTol - this.FeMntHeightRelTol; // compute relative tolerances for all values for (var i = 0; i < fieldNames.length; i++) { this[fieldNames[i]+'RelTol'] = 100 * Math.abs( this[fieldNames[i]+'AbsTol'] / this[fieldNames[i]] ); } ControlPanels.Update(); }, }; xOnLoad( AlBiruniModel.Update() ); ControlPanels.NewPanel( { Name: 'UnitsPanel', ModelRef: 'AlBiruniModel', NCols: 2, OnModelChange: function(field){ AlBiruniModel.Update(field); }, } ).AddHeader( { Text: 'Choose the Units and Angle Formats for the Calculators', ColSpan: 4, } ).AddRadiobuttonField( { Name: 'UnitsType', Label: 'Lengths', ValueType: 'int', Items: [ { Name: 'km/m (Metric)', Value: 0 }, { Name: 'mi/ft (Imp)', Value: 1 }, { Name: 'ft/ft (Imp)', Value: 2 } ] } ).AddRadiobuttonField( { Name: 'AngleFormat', Label: 'Angles', ValueType: 'int', Items: [ { Name: 'deg.', Value: 0 }, { Name: 'DM', Value: 1 }, { Name: 'DMS', Value: 2 } ] } ).Render(); ControlPanels.NewPanel( { Name: 'InputAlBiruniParametersPanel', ModelRef: 'AlBiruniModel', NCols: 3, OnModelChange: function(field){ AlBiruniModel.Update(field) }, Format: 'fix0', Digits: 2, FormatTab: true, ReadOnly: false, PanelFormat: 'InputNormalWidth' } ).AddHeader( { Text: 'Measured Parameters', ColSpan: 3, } ).AddHeader( { Text: 'Tolerances Absolute', ColSpan: 2, } ).AddHeader( { Text: 'Tolerances Relative', ColSpan: 1, } ).AddTextField( { Name: 'ObsHeight', Label: 'Observer Height h', UnitsData: 'HeightUnits', Inc: 10, LowerLimit: 0, UpperLimit: 11000, } ).AddTextField( { Name: 'ObsHeightAbsTol', Label: '+/-', UnitsData: 'HeightUnits', Inc: 0.1, LowerLimit: 0, UpperLimit: 11000, } ).AddTextField( { Name: 'ObsHeightRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'DipAngle', Label: 'Dip Angle α', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 0.05, LowerLimit: -45, UpperLimit: 45, } ).AddTextField( { Name: 'DipAngleAbsTol', Label: '+/-', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 0.005, LowerLimit: 0, UpperLimit: 45, } ).AddTextField( { Name: 'DipAngleRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'Pressure', Label: 'Pressure P', Units: 'mbar', Inc: 1, LowerLimit: 200, UpperLimit: 1200, } ).AddTextField( { Name: 'PressureAbsTol', Label: '+/-', Units: 'mbar', Inc: 0.1, LowerLimit: 0, UpperLimit: 1200, } ).AddTextField( { Name: 'PressureRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'TempC', Label: 'Temperature T', Units: '°C', Inc: 1, LowerLimit: -100, UpperLimit: 100, } ).AddTextField( { Name: 'TempCAbsTol', Label: '+/-', Units: '°C', Inc: 0.1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'TempCRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'TempGrad', Label: 'Gradient dT/dh', UnitsData: 'GradientUnits', Digits: 5, Inc: 0.0005, LowerLimit: -1, UpperLimit: 1, } ).AddTextField( { Name: 'TempGradAbsTol', Label: '+/-', UnitsData: 'GradientUnits', Digits: 5, Inc: 0.00005, LowerLimit: 0, UpperLimit: 1, } ).AddTextField( { Name: 'TempGradRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'RefrCoeff', Label: 'Refraction k', Units: '', Digits: 3, Inc: 0.01, LowerLimit: -10, UpperLimit: 10, } ).AddTextField( { Name: 'RefrCoeffAbsTol', Label: '+/-', Units: '', Digits: 3, Inc: 0.001, LowerLimit: 0, UpperLimit: 10, } ).AddTextField( { Name: 'RefrCoeffRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'LightRad', Label: 'Light Radius r', UnitsData: 'BigLengthUnits', Inc: 1000, } ).AddTextField( { Name: 'LightRadAbsTol', Label: '+/-', UnitsData: 'BigLengthUnits', Inc: 100, LowerLimit: 0, } ).AddTextField( { Name: 'LightRadRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'LightCurve', Label: 'Light Curve c', UnitsData: 'InvLengthUnits', Format: 'sci', Inc: 1e-9, LowerLimit: -1e-6, UpperLimit: 1e-6, } ).AddTextField( { Name: 'LightCurveAbsTol', Label: '+/-', UnitsData: 'InvLengthUnits', Format: 'sci', Inc: 1e-10, LowerLimit: 0, UpperLimit: 1e-6, } ).AddTextField( { Name: 'LightCurveRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).Render(); ControlPanels.NewPanel( { Name: 'OutputRadiusOfEarthPanel', ModelRef: 'AlBiruniModel', NCols: 4, OnModelChange: function(field){ AlBiruniModel.Update(field) }, FormatTab: true, Format: 'fix0', Digits: 2, ReadOnly: true, PanelFormat: 'InputSmallerWidth' } ).AddHeader( { Text: 'Result', ColSpan: 1, } ).AddHeader( { Text: 'Exact', ColSpan: 2, } ).AddHeader( { Text: 'Mean', ColSpan: 2, } ).AddHeader( { Text: 'Accuracy Absolute', ColSpan: 2, } ).AddHeader( { Text: 'Accuracy Relative', ColSpan: 1, } ).AddTextField( { Name: 'R', Label: 'Radius Earth R', UnitsData: 'BigLengthUnits', } ).AddTextField( { Name: 'RMean', Label: '', UnitsData: 'BigLengthUnits', } ).AddTextField( { Name: 'RAbsTol', Label: '+/-', UnitsData: 'BigLengthUnits', } ).AddTextField( { Name: 'RRelTol', Label: '+/-', Units: '%', } ).Render(); //// Mountain Calculator Panel //// ControlPanels.NewPanel( { Name: 'InputMountainHeightParametersPanel', ModelRef: 'AlBiruniModel', NCols: 3, OnModelChange: function(field){ AlBiruniModel.Update(field) }, Format: 'fix0', Digits: 2, FormatTab: true, ReadOnly: false, PanelFormat: 'InputNormalWidth' } ).AddHeader( { Text: 'Mountain Measurements', ColSpan: 3, } ).AddHeader( { Text: 'Tolerances Absolute', ColSpan: 2, } ).AddHeader( { Text: 'Tolerances Relative', ColSpan: 1, } ).AddTextField( { Name: 'DistAB', Label: 'Distance AB', UnitsData: 'LengthUnits', Digits: 3, Inc: 10, LowerLimit: 0, UpperLimit: 11000, } ).AddTextField( { Name: 'DistABAbsTol', Label: '+/-', UnitsData: 'LengthUnits', Digits: 3, Inc: 0.1, LowerLimit: 0, UpperLimit: 11000, } ).AddTextField( { Name: 'DistABRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'Alpha', Label: 'Angle α at A', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 1, LowerLimit: 0, UpperLimit: 90, } ).AddTextField( { Name: 'AlphaAbsTol', Label: '+/-', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 0.005, LowerLimit: 0, UpperLimit: 45, } ).AddTextField( { Name: 'AlphaRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'Beta', Label: 'Angle β at B', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 1, LowerLimit: 0, UpperLimit: 90, } ).AddTextField( { Name: 'BetaAbsTol', Label: '+/-', InputFormat: 'dms', Format: 'dms', UnitsData: 'AngleFormats', Digits: 4, Inc: 0.005, LowerLimit: 0, UpperLimit: 45, } ).AddTextField( { Name: 'BetaRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).AddTextField( { Name: 'MntRefrCoeff', Label: 'Refraction k', Units: '', Digits: 3, Inc: 0.01, LowerLimit: -10, UpperLimit: 10, } ).AddTextField( { Name: 'MntRefrCoeffAbsTol', Label: '+/-', Units: '', Digits: 3, Inc: 0.001, LowerLimit: 0, UpperLimit: 10, } ).AddTextField( { Name: 'MntRefrCoeffRelTol', Label: '+/-', Units: '%', Inc: 1, LowerLimit: 0, UpperLimit: 100, } ).Render(); ControlPanels.NewPanel( { Name: 'OutputMountainHeightPanel', ModelRef: 'AlBiruniModel', NCols: 4, OnModelChange: function(field){ AlBiruniModel.Update(field) }, FormatTab: true, Format: 'fix0', Digits: 3, ReadOnly: true, PanelFormat: 'InputSmallerWidth' } ).AddHeader( { Text: 'Mount. H', ColSpan: 1, } ).AddHeader( { Text: 'Exact', ColSpan: 2, } ).AddHeader( { Text: 'Mean', ColSpan: 2, } ).AddHeader( { Text: 'Accuracy Absolute', ColSpan: 2, } ).AddHeader( { Text: 'Accuracy Relative', ColSpan: 1, } ).AddTextField( { Name: 'FeMntHeight', Label: 'Flat Earth', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'FeMntHeightMean', Label: '', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'FeMntHeightAbsTol', Label: '+/-', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'FeMntHeightRelTol', Label: '+/-', Units: '%', } ).AddTextField( { Name: 'GlobeMntHeight', Label: 'Globe Earth', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeMntHeightMean', Label: '', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeMntHeightAbsTol', Label: '+/-', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeMntHeightRelTol', Label: '+/-', Units: '%', } ).AddTextField( { Name: 'GlobeFlatDiff', Label: 'Globe - Flat', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeFlatDiffMean', Label: '', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeFlatDiffAbsTol', Label: '+/-', UnitsData: 'HeightUnits', } ).AddTextField( { Name: 'GlobeFlatDiffRelTol', Label: '+/-', Units: '%', } ).Render(); </jscript>