From 823b38ac6ef54e058dac874d07cfaa6184ed933d Mon Sep 17 00:00:00 2001
From: Uskompuf <22492406+Uskompuf@users.noreply.github.com>
Date: Tue, 5 Apr 2022 17:12:19 +1000
Subject: [PATCH] very initial hysplit
---
build.sh | 2 +-
css/skewt.css | 1 -
index.html | 31 -
js/app.js | 11 -
js/skewt.js | 1783 -------------------------------------------------
js/tracker.js | 222 +-----
6 files changed, 32 insertions(+), 2018 deletions(-)
delete mode 100644 css/skewt.css
delete mode 100644 js/skewt.js
diff --git a/build.sh b/build.sh
index 3e37053..2d21b16 100755
--- a/build.sh
+++ b/build.sh
@@ -4,7 +4,7 @@
echo -n "Compiling CSS... "
cd css
rm -f mobile.css
-cat base.css skeleton.css layout.css habitat-font.css main.css leaflet.css leaflet.fullscreen.css skewt.css > mobile.tmp
+cat base.css skeleton.css layout.css habitat-font.css main.css leaflet.css leaflet.fullscreen.css > mobile.tmp
java -jar "../tools/yuicompressor-2.4.8.jar" --type=css mobile.tmp > mobile.css
rm -f mobile.tmp
cd ..
diff --git a/css/skewt.css b/css/skewt.css
deleted file mode 100644
index de766e8..0000000
--- a/css/skewt.css
+++ /dev/null
@@ -1 +0,0 @@
-.skew-t{position:relative;padding:0px}.skew-t .fnt{transition:font .3s;font:10px Arial;font-family:-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif}.skew-t .mainsvg{background-color:transparent}.skew-t .controls,.skew-t .range-container{margin-top:10px}.skew-t .controls .buttons,.skew-t .range-container .buttons{flex-grow:1;margin:3px;padding:3px 0 3.2px 0;border-radius:10px;text-align:center;cursor:pointer;line-height:1.1em;background-color:#dcdcdc}.skew-t .controls .buttons.clicked,.skew-t .range-container .buttons.clicked{background-color:#969696;color:white}.skew-t .controls .buttons.units,.skew-t .range-container .buttons.units{flex-grow:0;width:80px}.skew-t .controls .buttons.noclick,.skew-t .range-container .buttons.noclick{cursor:initial}.skew-t .controls .row,.skew-t .range-container .row{display:flex;flex-wrap:wrap}.skew-t .controls{box-sizing:border-box;width:100%;display:flex}.skew-t .controls .values{flex-grow:1;margin:3px;padding:3px;text-align:center;border-radius:10px;border:1px solid #dcdcdc;min-width:40px}.skew-t .skewt-range-des{width:20%}.skew-t .skewt-range-val{width:15%;white-space:nowrap}.skew-t .checkbox-container{width:100%;line-height:20px}.skew-t .select-units :first-child{line-height:20px}.skew-t .axis path,.skew-t .axis line{fill:none;stroke:#000;stroke-width:1px;shape-rendering:crispEdges}.skew-t .axis{fill:#000}.skew-t .y.axis{font-size:10px}.skew-t .y.axis.hght{font-size:10px;fill:red}.skew-t .x.axis{font-size:10px}.skew-t .y.axis.ticks text{display:none}.skew-t .y.axis.hght-ticks text{display:none}.skew-t .y.axis.hght-ticks line{stroke:red}.skew-t .skewt-line{fill:none;stroke-width:1.5px;stroke-opacity:.5}.skew-t .skewt-line.highlight-line{stroke-opacity:1;stroke-width:2px}.skew-t .temp{fill:none;stroke-width:1.5px;stroke-opacity:.5;stroke:red}.skew-t .temp.highlight-line{stroke-opacity:1;stroke-width:2px}.skew-t .dwpt{fill:none;stroke-width:1.5px;stroke-opacity:.5;stroke:blue}.skew-t .dwpt.highlight-line{stroke-opacity:1;stroke-width:2px}.skew-t .parcel{fill:none;stroke-width:1.5px;stroke-opacity:.5;stroke:green;stroke-opacity:.3}.skew-t .parcel.highlight-line{stroke-opacity:1;stroke-width:2px}.skew-t .cond-level{fill:none;stroke-width:1.5px;stroke-opacity:.5;stroke-width:1px;stroke:rgba(128,128,128,0.8);stroke-opacity:.15}.skew-t .cond-level.highlight-line{stroke-opacity:1;stroke-width:2px}.skew-t .cond-level.highlight-line{stroke-width:1px}.skew-t .gridline{stroke-width:.5px;stroke-opacity:.3;fill:none}.skew-t .gridline.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .tempzero{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:#aaa;stroke-width:1.25px}.skew-t .tempzero.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .dryadiabat{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:green}.skew-t .dryadiabat.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .templine{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:red}.skew-t .templine.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .pressure{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:#787878}.skew-t .pressure.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .moistadiabat{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:green;stroke-dasharray:5}.skew-t .moistadiabat.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .isohume{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:blue;stroke-dasharray:2}.skew-t .isohume.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .elr{stroke-width:.5px;stroke-opacity:.3;fill:none;stroke:purple;stroke-opacity:.03;stroke-width:3px}.skew-t .elr.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .elr.highlight-line{stroke-opacity:.7}.skew-t .sigline{stroke-width:.5px;stroke-opacity:.3;fill:none}.skew-t .sigline.highlight-line{stroke-opacity:1;stroke-width:1px}.skew-t .sigline.surface{stroke:green}.skew-t .sigline.tropopause-level{stroke:blue}.skew-t .windbarb{stroke:#000;stroke-width:.75px;fill:none}.skew-t .windbarb .barblines{opacity:.4}.skew-t .windbarb .barblines.highlight-line{opacity:1}.skew-t .windbarb .barblines.hidden{display:none}.skew-t .windbarb .windtext{opacity:.4;dominant-baseline:central;font-size:10px;fill:black;stroke-width:0}.skew-t .windbarb .windtext.highlight-line{opacity:1}.skew-t .windbarb .windtext.hidden{display:none}.skew-t .flag{fill:#000}.skew-t .overlay{fill:none;pointer-events:all}.skew-t .focus.tmpc circle{fill:red;stroke:none}.skew-t .focus.dwpc circle{fill:blue;stroke:none}.skew-t .focus text{font-size:14px}.skew-t .skewt-wind-arrow{alignment-baseline:middle;text-anchor:middle;fill:black;font-size:16px;font-weight:bold}.skew-t .range-container-extra{margin-top:10px;margin-top:0px}.skew-t .range-container-extra .buttons{flex-grow:1;margin:3px;padding:3px 0 3.2px 0;border-radius:10px;text-align:center;cursor:pointer;line-height:1.1em;background-color:#dcdcdc}.skew-t .range-container-extra .buttons.clicked{background-color:#969696;color:white}.skew-t .range-container-extra .buttons.units{flex-grow:0;width:80px}.skew-t .range-container-extra .buttons.noclick{cursor:initial}.skew-t .range-container-extra .row{display:flex;flex-wrap:wrap}.skew-t .skewt-ranges{all:revert;-webkit-appearance:none;color:white;background-color:transparent;width:60%}.skew-t .skewt-ranges:focus{outline:none}.skew-t .skewt-ranges::-webkit-slider-runnable-track{all:revert;-webkit-appearance:none;background:#dcdcdc;border-radius:16px;height:16px;padding:2px}.skew-t .skewt-ranges::-webkit-slider-thumb{all:revert;-webkit-appearance:none;border:1px solid #646464;height:12px;width:12px;border-radius:12px;background:#ffffff;cursor:pointer;box-shadow:1px 1px 1px #000000}.skew-t .flex-break{flex-basis:100%}.skew-t .cloud-container{overflow:hidden;position:absolute;width:20px;opacity:.8}.skew-t .cloud-container .cloud{position:absolute;width:10px}
\ No newline at end of file
diff --git a/index.html b/index.html
index 72253c9..c7c6d50 100644
--- a/index.html
+++ b/index.html
@@ -49,9 +49,6 @@
-
Weather
@@ -318,7 +288,6 @@ Chase Mode
-
diff --git a/js/app.js b/js/app.js
index 310ad62..b16059d 100644
--- a/js/app.js
+++ b/js/app.js
@@ -853,17 +853,6 @@ $(window).ready(function() {
['Global', [
['rainviewer', 'RainViewer'],
['rainviewer-coverage', 'RainViewer Coverage'],
- ]],
- ['North America', [
- ['nexrad-n0q-900913', 'NEXRAD Base Reflectivity'],
- ['goes-ir-4km-900913', 'GOES NA Infrared ~4km'],
- ['goes-wv-4km-900913', 'GOES NA Water Vapor ~4km'],
- ['goes-vis-1km-900913', 'GOES NA Visible ~1km'],
- ['q2-n1p-900913', 'Q2 1 Hour Precipitation'],
- ['q2-p24h-900913', 'Q2 24 Hour Precipitation'],
- ['q2-p48h-900913', 'Q2 48 Hour Precipitation'],
- ['q2-p72h-900913', 'Q2 72 Hour Precipitation'],
- ['q2-hsr-900913', 'MRMS Hybrid-Scan Reflectivity Composite.']
]]
];
diff --git a/js/skewt.js b/js/skewt.js
deleted file mode 100644
index 1838649..0000000
--- a/js/skewt.js
+++ /dev/null
@@ -1,1783 +0,0 @@
-(function(){'use strict';// Linear interpolation
-// The values (y1 and y2) can be arrays
-//export
-function linearInterpolate(x1, y1, x2, y2, x) {
- if (x1 == x2) {
- return y1;
- }
- const w = (x - x1) / (x2 - x1);
-
- if (Array.isArray(y1)) {
- return y1.map((y1, i) => y1 * (1 - w) + y2[i] * w);
- }
- return y1 * (1 - w) + y2 * w;
-}
-
-// Sampling at at targetXs with linear interpolation
-// xs and ys must have the same length.
-//export
-function sampleAt(xs, ys, targetXs) {
- const descOrder = xs[0] > xs[1];
- return targetXs.map((tx) => {
- let index = xs.findIndex((x) => (descOrder ? x <= tx : x >= tx));
- if (index == -1) {
- index = xs.length - 1;
- } else if (index == 0) {
- index = 1;
- }
- return linearInterpolate(xs[index - 1], ys[index - 1], xs[index], ys[index], tx);
- });
-}
-
-// x?s must be sorted in ascending order.
-// x?s and y?s must have the same length.
-// return [x, y] or null when no intersection found.
-//export
-function firstIntersection(x1s, y1s, x2s, y2s) {
- // Find all the points in the intersection of the 2 x ranges
- const min = Math.max(x1s[0], x2s[0]);
- const max = Math.min(x1s[x1s.length - 1], x2s[x2s.length - 1]);
- const xs = Array.from(new Set([...x1s, ...x2s]))
- .filter((x) => x >= min && x <= max)
- .sort((a, b) => (Number(a) > Number(b) ? 1 : -1));
- // Interpolate the lines for all the points of that intersection
- const iy1s = sampleAt(x1s, y1s, xs);
- const iy2s = sampleAt(x2s, y2s, xs);
- // Check if each segment intersect
- for (let index = 0; index < xs.length - 1; index++) {
- const y11 = iy1s[index];
- const y21 = iy2s[index];
- const x1 = xs[index];
- if (y11 == y21) {
- return [x1, y11];
- }
- const y12 = iy1s[index + 1];
- const y22 = iy2s[index + 1];
- if (Math.sign(y21 - y11) != Math.sign(y22 - y12)) {
- const x2 = xs[index + 1];
- const width = x2 - x1;
- const slope1 = (y12 - y11) / width;
- const slope2 = (y22 - y21) / width;
- const dx = (y21 - y11) / (slope1 - slope2);
- const dy = dx * slope1;
- return [x1 + dx, y11 + dy];
- }
- }
- return null;
-}
-
-//export
-function zip(a, b) {
- return a.map((v, i) => [v, b[i]]);
-}
-
-//export
-function scaleLinear(from, to) {
- const scale = (v) => sampleAt(from, to, [v])[0];
- scale.invert = (v) => sampleAt(to, from, [v])[0];
- return scale;
-}
-
-//export
-function scaleLog(from, to) {
- from = from.map(Math.log);
- const scale = (v) => sampleAt(from, to, [Math.log(v)])[0];
- scale.invert = (v) => Math.exp(sampleAt(to, from, [v])[0]);
- return scale;
-}
-
-//export
-function line(x, y) {
- return (d) => {
- const points = d.map((v) => x(v).toFixed(1) + "," + y(v).toFixed(1));
- return "M" + points.join("L");
- };
-}
-
-//export
-function lerp(v0, v1, weight) {
- return v0 + weight * (v1 - v0);
-}
-
-var math = {
- linearInterpolate,
- sampleAt,
- zip,
- firstIntersection,
- scaleLinear,
- scaleLog,
- line,
- lerp,
-};// Gas constant for dry air at the surface of the Earth
-const Rd = 287;
-// Specific heat at constant pressure for dry air
-const Cpd = 1005;
-// Molecular weight ratio
-const epsilon = 18.01528 / 28.9644;
-// Heat of vaporization of water
-const Lv = 2501000;
-// Ratio of the specific gas constant of dry air to the specific gas constant for water vapour
-const satPressure0c = 6.112;
-// C + celsiusToK -> K
-const celsiusToK = 273.15;
-const L$1 = -6.5e-3;
-const g = 9.80665;
-
-/**
- * Computes the temperature at the given pressure assuming dry processes.
- *
- * t0 is the starting temperature at p0 (degree Celsius).
- */
-
-
-
-//export
-function dryLapse(p, tK0, p0) {
- return tK0 * Math.pow(p / p0, Rd / Cpd);
-}
-
-
-//to calculate isohume lines:
-//1. Obtain saturation vapor pressure at a specific temperature = partial pressure at a specific temp where the air will be saturated.
-//2. Mixing ratio: Use the partial pressure where air will be saturated and the actual pressure to determine the degree of mixing, thus what % of air is water.
-//3. Having the mixing ratio at the surface, calculate the vapor pressure at different pressures.
-//4. Dewpoint temp can then be calculated with the vapor pressure.
-
-// Computes the mixing ration of a gas.
-//export
-function mixingRatio(partialPressure, totalPressure, molecularWeightRatio = epsilon) {
- return (molecularWeightRatio * partialPressure) / (totalPressure - partialPressure);
-}
-
-// Computes the saturation mixing ratio of water vapor.
-//export
-function saturationMixingRatio(p, tK) {
- return mixingRatio(saturationVaporPressure(tK), p);
-}
-
-// Computes the saturation water vapor (partial) pressure
-//export
-function saturationVaporPressure(tK) {
- const tC = tK - celsiusToK;
- return satPressure0c * Math.exp((17.67 * tC) / (tC + 243.5));
-}
-
-// Computes the temperature gradient assuming liquid saturation process.
-//export
-function moistGradientT(p, tK) {
- const rs = saturationMixingRatio(p, tK);
- const n = Rd * tK + Lv * rs;
- const d = Cpd + (Math.pow(Lv, 2) * rs * epsilon) / (Rd * Math.pow(tK, 2));
- return (1 / p) * (n / d);
-}
-
-// Computes water vapor (partial) pressure.
-//export
-function vaporPressure(p, mixing) {
- return (p * mixing) / (epsilon + mixing);
-}
-
-// Computes the ambient dewpoint given the vapor (partial) pressure.
-//export
-function dewpoint(p) {
- const val = Math.log(p / satPressure0c);
- return celsiusToK + (243.5 * val) / (17.67 - val);
-}
-
-//export
-function getElevation(p, p0 = 1013.25) {
- const t0 = 288.15;
- //const p0 = 1013.25;
- return (t0 / L$1) * (Math.pow(p / p0, (-L$1 * Rd) / g) - 1);
-}
-
-//export
-function getElevation2(p, refp = 1013.25) { //pressure altitude with NOAA formula (https://en.wikipedia.org/wiki/Pressure_altitude)
- return 145366.45 * (1 - Math.pow(p / refp, 0.190284)) / 3.28084;
-}
-
-//export
-function pressureFromElevation(e, refp = 1013.25) {
- e = e * 3.28084;
- return Math.pow((-(e / 145366.45 - 1)), 1 / 0.190284) * refp;
-}
-
-//export
-function getSurfaceP(surfElev, refElev = 110.8, refP = 1000) { //calculate surface pressure at surfelev, from reference elev and ref pressure.
- let elevD = surfElev - refElev;
- return pressureFromElevation(elevD, refP);
-}
-
-//export
-
-/**
- * @param params = {temp, gh, level}
- * @param surface temp, pressure and dewpoint
- */
-
-
-function parcelTrajectory(params, steps, sfcT, sfcP, sfcDewpoint) {
-
- //remove invalid or NaN values in params
- for (let i = 0; i < params.temp.length; i++) {
- let inval = false;
- for (let p in params) if (!params[p][i] && params[p][i] !== 0) inval = true;
- if (inval) for (let p in params) params[p].splice(i, 1);
- }
-
- const parcel = {};
- const dryGhs = [];
- const dryPressures = [];
- const dryTemps = []; //dry temps from surface temp, which can be greater than templine start
- const dryDewpoints = [];
- const dryTempsTempline = []; //templine start
-
- const mRatio = mixingRatio(saturationVaporPressure(sfcDewpoint), sfcP);
-
- const pToEl = math.scaleLog(params.level, params.gh);
- const minEl = pToEl(sfcP);
- const maxEl = Math.max(minEl, params.gh[params.gh.length - 1]);
- const stepEl = (maxEl - minEl) / steps;
-
- const moistLineFromEandT = (elevation, t) => {
- //this calculates a moist line from elev and temp to the intersection of the temp line if the intersection exists otherwise very high cloudtop
- const moistGhs = [], moistPressures = [], moistTemps = [];
- let previousP = pToEl.invert(elevation);
- for (; elevation < maxEl + stepEl; elevation += stepEl) {
- const p = pToEl.invert(elevation);
- t = t + (p - previousP) * moistGradientT(p, t);
- previousP = p;
- moistGhs.push(elevation);
- moistPressures.push(p);
- moistTemps.push(t);
- }
- let moist = math.zip(moistTemps, moistPressures);
- let cloudTop, pCloudTop;
- const equilibrium = math.firstIntersection(moistGhs, moistTemps, params.gh, params.temp);
-
- if (moistTemps.length){
- let i1 = params.gh.findIndex(e=> e>moistGhs[1]),i2=i1-1;
- if (i2>0){
- let tempIp = math.linearInterpolate(params.gh[i1],params.temp[i1],params.gh[i2],params.temp[i2],moistGhs[1]);
- if (moistTemps[1] < tempIp){
- if (!equilibrium){
- //console.log("%c no Equilibrium found, cut moist temp line short","color:green");
- //no intersection found, so use point one as the end.
- equilibrium = [moistGhs[1], moistTemps[1]];
-
- }
- }
- }
- }
- if (equilibrium) {
- cloudTop = equilibrium[0];
- pCloudTop = pToEl.invert(equilibrium[0]);
- moist = moist.filter((pt) => pt[1] >= pCloudTop);
- moist.push([equilibrium[1], pCloudTop]);
- } else { //does not intersect, very high CBs
- cloudTop = 100000;
- pCloudTop = pToEl.invert(cloudTop);
- }
- return { moist, cloudTop, pCloudTop };
- };
-
-
- for (let elevation = minEl; elevation <= maxEl; elevation += stepEl) {
- const p = pToEl.invert(elevation);
- const t = dryLapse(p, sfcT, sfcP);
- const dp = dewpoint(vaporPressure(p, mRatio));
- dryGhs.push(elevation);
- dryPressures.push(p);
- dryTemps.push(t); //dry adiabat line from templine surfc
- dryDewpoints.push(dp); //isohume line from dewpoint line surfc
-
- const t2 = dryLapse(p, params.temp[0], sfcP);
- dryTempsTempline.push(t2);
- }
-
- const cloudBase = math.firstIntersection(dryGhs, dryTemps, dryGhs, dryDewpoints);
- //intersection dry adiabat from surface temp to isohume from surface dewpoint, if dp==surf temp, then cloudBase will be null
-
- let thermalTop = math.firstIntersection(dryGhs, dryTemps, params.gh, params.temp);
- //intersection of dryadiabat from surface to templine. this will be null if stable, leaning to the right
-
- let LCL = math.firstIntersection(dryGhs, dryTempsTempline, dryGhs, dryDewpoints);
- //intersection dry adiabat from surface temp to isohume from surface dewpoint, if dp==surf temp, then cloudBase will be null
-
- let CCL = math.firstIntersection(dryGhs, dryDewpoints, params.gh, params.temp);
- //console.log(CCL, dryGhs, dryDewpoints, params.gh, params.temp );
- //intersection of isohume line with templine
-
-
- //console.log(cloudBase, thermalTop, LCL, CCL);
-
- if (LCL && LCL.length) {
- parcel.LCL = LCL[0];
- let LCLp = pToEl.invert(LCL[0]);
- parcel.isohumeToDry = [].concat(
- math.zip(dryTempsTempline, dryPressures).filter(p => p[1] >= LCLp),
- [[LCL[1], LCLp]],
- math.zip(dryDewpoints, dryPressures).filter(p => p[1] >= LCLp).reverse()
- );
- }
-
- if (CCL && CCL.length) {
- //parcel.CCL=CCL[0];
- let CCLp = pToEl.invert(CCL[0]);
- parcel.TCON = dryLapse(sfcP, CCL[1], CCLp);
-
- //check if dryTempsTCON crosses temp line at CCL, if lower, then inversion exists and TCON, must be moved.
-
- //console.log(parcel.TCON)
- let dryTempsTCON=[];
-
- for(let CCLtempMoreThanTempLine=false; !CCLtempMoreThanTempLine; parcel.TCON+=0.5){
-
- let crossTemp = [-Infinity];
-
- for (; crossTemp[0] < CCL[0]; parcel.TCON += 0.5) {
- //if (crossTemp[0]!=-Infinity) console.log("TCON MUST BE MOVED");
- dryTempsTCON = [];
- for (let elevation = minEl; elevation <= maxEl; elevation += stepEl) { //line from isohume/temp intersection to TCON
- const t = dryLapse(pToEl.invert(elevation), parcel.TCON, sfcP);
- dryTempsTCON.push(t);
- }
- crossTemp = math.firstIntersection(dryGhs, dryTempsTCON, params.gh, params.temp) || [-Infinity]; //intersection may return null
-
-
- }
-
- parcel.TCON -= 0.5;
-
- if (crossTemp[0] > CCL[0]) {
- CCL = math.firstIntersection(dryGhs, dryTempsTCON, dryGhs, dryDewpoints);
- //now check if temp at CCL is more than temp line, if not, has hit another inversion and parcel.TCON must be moved further
- let i2= params.gh.findIndex(gh => gh>CCL[0]), i1= i2-1;
- if (i1>=0){
- let tempLineIp=math.linearInterpolate(params.gh[i1], params.temp[i1], params.gh[i2], params.temp[i2], CCL[0]);
- if (CCL[1] > tempLineIp) {
- CCLtempMoreThanTempLine = true;
- //console.log("%c CCL1 is more than templine", "color:green", CCL[1], tempLineIp);
- }
- }
- }
- }
- parcel.TCON -= 0.5;
-
-
- parcel.CCL = CCL[0];
- CCLp = pToEl.invert(CCL[0]);
-
- parcel.isohumeToTemp = [].concat(
- math.zip(dryDewpoints, dryPressures).filter(p => p[1] >= CCLp),
- [[CCL[1], CCLp]],
- math.zip(dryTempsTCON, dryPressures).filter(p => p[1] >= CCLp).reverse()
- );
- parcel.moistFromCCL = moistLineFromEandT(CCL[0], CCL[1]).moist;
- }
-
- parcel.surface = params.gh[0];
-
-
- if (!thermalTop) {
- return parcel;
- } else {
- parcel.origThermalTop = thermalTop[0];
- }
-
- if (thermalTop && cloudBase && cloudBase[0] < thermalTop[0]) {
-
- thermalTop = cloudBase;
-
- const pCloudBase = pToEl.invert(cloudBase[0]);
-
- Object.assign(
- parcel,
- moistLineFromEandT(cloudBase[0], cloudBase[1]) //add to parcel: moist = [[moistTemp,moistP]...], cloudTop and pCloudTop.
- );
-
- const isohume = math.zip(dryDewpoints, dryPressures).filter((pt) => pt[1] > pCloudBase); //filter for pressures higher than cloudBase, thus lower than cloudBase
- isohume.push([cloudBase[1], pCloudBase]);
-
-
-
- //parcel.pCloudTop = params.level[params.level.length - 1];
-
-
-
- //parcel.cloudTop = cloudTop;
- //parcel.pCloudTop = pCloudTop;
-
- //parcel.moist = moist;
-
- parcel.isohume = isohume;
-
- }
-
- let pThermalTop = pToEl.invert(thermalTop[0]);
- const dry = math.zip(dryTemps, dryPressures).filter((pt) => pt[1] > pThermalTop);
- dry.push([thermalTop[1], pThermalTop]);
-
- parcel.dry = dry;
- parcel.pThermalTop = pThermalTop;
- parcel.elevThermalTop = thermalTop[0];
-
-
-
- //console.log(parcel);
- return parcel;
-}
-
-var atm = {
- dryLapse,
- mixingRatio,
- saturationVaporPressure,
- moistGradientT,
- vaporPressure,
- dewpoint,
- getElevation,
- getElevation2,
- pressureFromElevation,
- getSurfaceP,
- parcelTrajectory,
-};function lerp$1(v0, v1, weight) {
- return v0 + weight * (v1 - v0);
-}
-/////
-
-
-
-
-const lookup = new Uint8Array(256);
-
-for (let i = 0; i < 160; i++) {
- lookup[i] = clampIndex(24 * Math.floor((i + 12) / 16), 160);
-}
-
-
-
-// Compute the rain clouds cover.
-// Output an object:
-// - clouds: the clouds cover,
-// - width & height: dimension of the cover data.
-function computeClouds(ad, wdth = 1, hght = 200) { ////added wdth and hght, to improve performance ///supply own hrAlt altutude percentage distribution, based on pressure levels
- // Compute clouds data.
-
- //console.log("WID",wdth,hght);
-
- /////////convert to windy format
- //ad must be sorted;
-
- const logscale = (x, d, r) => { //log scale function D3, x is the value d is the domain [] and r is the range []
- let xlog = Math.log10(x),
- dlog = [Math.log10(d[0]), Math.log10(d[1])],
- delta_d = dlog[1] - dlog[0],
- delta_r = r[1] - r[0];
- return r[0] + ((xlog - dlog[0]) / delta_d) * delta_r;
- };
-
- let airData = {};
- let hrAltPressure = [], hrAlt = [];
- ad.forEach(a => {
- if (!a.press) return;
- if (a.rh == void 0 && a.dwpt && a.temp) {
- a.rh = 100 * (Math.exp((17.625 * a.dwpt) / (243.04 + a.dwpt)) / Math.exp((17.625 * a.temp) / (243.04 + a.temp))); ///August-Roche-Magnus approximation.
- }
- if (a.rh && a.press >= 100) {
- let p = Math.round(a.press);
- airData[`rh-${p}h`] = [a.rh];
- hrAltPressure.push(p);
- hrAlt.push(logscale(p, [1050, 100], [0, 100]));
- }
- });
-
- //fi x underground clouds, add humidty 0 element in airData wehre the pressure is surfcace pressure +1:
- airData[`rh-${(hrAltPressure[0] + 1)}h`] = [0];
- hrAlt.unshift(null, hrAlt[0]);
- hrAltPressure.unshift(null, hrAltPressure[0] + 1);
- hrAltPressure.pop(); hrAltPressure.push(null);
-
- ///////////
-
-
- const numX = airData[`rh-${hrAltPressure[1]}h`].length;
- const numY = hrAltPressure.length;
- const rawClouds = new Array(numX * numY);
-
- for (let y = 0, index = 0; y < numY; ++y) {
- if (hrAltPressure[y] == null) {
- for (let x = 0; x < numX; ++x) {
- rawClouds[index++] = 0.0;
- }
- } else {
- const weight = hrAlt[y] * 0.01;
- const pAdd = lerp$1(-60, -70, weight);
- const pMul = lerp$1(0.025, 0.038, weight);
- const pPow = lerp$1(6, 4, weight);
- const pMul2 = 1 - 0.8 * Math.pow(weight, 0.7);
- const rhRow = airData[`rh-${hrAltPressure[y]}h`];
- for (let x = 0; x < numX; ++x) {
- const hr = Number(rhRow[x]);
- let f = Math.max(0.0, Math.min((hr + pAdd) * pMul, 1.0));
- f = Math.pow(f, pPow) * pMul2;
- rawClouds[index++] = f;
- }
- }
- }
-
-
- // Interpolate raw clouds.
- const sliceWidth = wdth || 10;
- const width = sliceWidth * numX;
- const height = hght || 300;
- const clouds = new Array(width * height);
- const kh = (height - 1) * 0.01;
- const dx2 = (sliceWidth + 1) >> 1;
- let heightLookupIndex = 2 * height;
- const heightLookup = new Array(heightLookupIndex);
- const buffer = new Array(16);
- let previousY;
- let currentY = height;
-
- for (let j = 0; j < numY - 1; ++j) {
- previousY = currentY;
- currentY = Math.round(height - 1 - hrAlt[j + 1] * kh);
- const j0 = numX * clampIndex(j + 2, numY);
- const j1 = numX * clampIndex(j + 1, numY);
- const j2 = numX * clampIndex(j + 0, numY);
- const j3 = numX * clampIndex(j - 1, numY);
- let previousX = 0;
- let currentX = dx2;
- const deltaY = previousY - currentY;
- const invDeltaY = 1.0 / deltaY;
-
- for (let i = 0; i < numX + 1; ++i) {
- if (i == 0 && deltaY > 0) {
- const ry = 1.0 / deltaY;
- for (let l = 0; l < deltaY; l++) {
- heightLookup[--heightLookupIndex] = j;
- heightLookup[--heightLookupIndex] = Math.round(10000 * ry * l);
- }
- }
- const i0 = clampIndex(i - 2, numX);
- const i1 = clampIndex(i - 1, numX);
- const i2 = clampIndex(i + 0, numX);
- const i3 = clampIndex(i + 1, numX);
- buffer[0] = rawClouds[j0 + i0];
- buffer[1] = rawClouds[j0 + i1];
- buffer[2] = rawClouds[j0 + i2];
- buffer[3] = rawClouds[j0 + i3];
- buffer[4] = rawClouds[j1 + i0];
- buffer[5] = rawClouds[j1 + i1];
- buffer[6] = rawClouds[j1 + i2];
- buffer[7] = rawClouds[j1 + i3];
- buffer[8] = rawClouds[j2 + i0];
- buffer[9] = rawClouds[j2 + i1];
- buffer[10] = rawClouds[j2 + i2];
- buffer[11] = rawClouds[j2 + i3];
- buffer[12] = rawClouds[j3 + i0];
- buffer[13] = rawClouds[j3 + i1];
- buffer[14] = rawClouds[j3 + i2];
- buffer[15] = rawClouds[j3 + i3];
-
- const topLeft = currentY * width + previousX;
- const dx = currentX - previousX;
- const fx = 1.0 / dx;
-
- for (let y = 0; y < deltaY; ++y) {
- let offset = topLeft + y * width;
- for (let x = 0; x < dx; ++x) {
- const black = step(bicubicFiltering(buffer, fx * x, invDeltaY * y) * 160.0);
- clouds[offset++] = 255 - black;
- }
- }
-
- previousX = currentX;
- currentX += sliceWidth;
-
- if (currentX > width) {
- currentX = width;
- }
- }
- }
-
- return { clouds, width, height };
-}
-
-function clampIndex(index, size) {
- return index < 0 ? 0 : index > size - 1 ? size - 1 : index;
-}
-
-function step(x) {
- return lookup[Math.floor(clampIndex(x, 160))];
-}
-
-function cubicInterpolate(y0, y1, y2, y3, m) {
- const a0 = -y0 * 0.5 + 3.0 * y1 * 0.5 - 3.0 * y2 * 0.5 + y3 * 0.5;
- const a1 = y0 - 5.0 * y1 * 0.5 + 2.0 * y2 - y3 * 0.5;
- const a2 = -y0 * 0.5 + y2 * 0.5;
- return a0 * m ** 3 + a1 * m ** 2 + a2 * m + y1;
-}
-
-function bicubicFiltering(m, s, t) {
- return cubicInterpolate(
- cubicInterpolate(m[0], m[1], m[2], m[3], s),
- cubicInterpolate(m[4], m[5], m[6], m[7], s),
- cubicInterpolate(m[8], m[9], m[10], m[11], s),
- cubicInterpolate(m[12], m[13], m[14], m[15], s),
- t
- );
-}
-
-// Draw the clouds on a canvas.
-// This function is useful for debugging.
-function cloudsToCanvas({ clouds, width, height, canvas }) {
- if (canvas == null) {
- canvas = document.createElement("canvas");
- }
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext("2d");
- let imageData = ctx.getImageData(0, 0, width, height);
- let imgData = imageData.data;
-
-
- let srcOffset = 0;
- let dstOffset = 0;
- for (let x = 0; x < width; ++x) {
- for (let y = 0; y < height; ++y) {
- const color = clouds[srcOffset++];
- imgData[dstOffset++] = color;
- imgData[dstOffset++] = color;
- imgData[dstOffset++] = color;
- imgData[dstOffset++] = color < 245 ? 255 : 0;
- }
- }
-
-
- ctx.putImageData(imageData, 0, 0);
- ctx.drawImage(canvas, 0, 0, width, height);
-
- return canvas;
-}
-
-var clouds = {
- computeClouds,
- cloudsToCanvas
-};/* eslint-disable */
-const t={};
-function n(t,n){return t
n?1:t>=n?0:NaN}function e(t){var e;return 1===t.length&&(e=t,t=function(t,r){return n(e(t),r)}),{left:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)<0?r=o+1:i=o;}return r},right:function(n,e,r,i){for(null==r&&(r=0),null==i&&(i=n.length);r>>1;t(n[o],e)>0?i=o:r=o+1;}return r}}}var r=e(n),i=r.right;var o=Math.sqrt(50),a=Math.sqrt(10),u=Math.sqrt(2);function s(t,n,e){var r,i,o,a,u=-1;if(e=+e,(t=+t)===(n=+n)&&e>0)return [t];if((r=n0)for(t=Math.ceil(t/a),n=Math.floor(n/a),o=new Array(i=Math.ceil(n-t+1));++u=0?(s>=o?10:s>=a?5:s>=u?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(s>=o?10:s>=a?5:s>=u?2:1)}var c=Array.prototype.slice;function h(t){return t}var f=1e-6;function p(t){return "translate("+(t+.5)+",0)"}function d(t){return "translate(0,"+(t+.5)+")"}function g$1(t){return function(n){return +t(n)}}function v(t){var n=Math.max(0,t.bandwidth()-1)/2;return t.round()&&(n=Math.round(n)),function(e){return +t(e)+n}}function m(){return !this.__axis}function y(t,n){var e=[],r=null,i=null,o=6,a=6,u=3,s=1===t||4===t?-1:1,l=4===t||2===t?"x":"y",y=1===t||3===t?p:d;function _(c){var p=null==r?n.ticks?n.ticks.apply(n,e):n.domain():r,d=null==i?n.tickFormat?n.tickFormat.apply(n,e):h:i,_=Math.max(o,0)+u,w=n.range(),x=+w[0]+.5,b=+w[w.length-1]+.5,M=(n.bandwidth?v:g$1)(n.copy()),k=c.selection?c.selection():c,N=k.selectAll(".domain").data([null]),A=k.selectAll(".tick").data(p,n).order(),E=A.exit(),S=A.enter().append("g").attr("class","tick"),T=A.select("line"),P=A.select("text");N=N.merge(N.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),A=A.merge(S),T=T.merge(S.append("line").attr("stroke","currentColor").attr(l+"2",s*o)),P=P.merge(S.append("text").attr("fill","currentColor").attr(l,s*_).attr("dy",1===t?"0em":3===t?"0.71em":"0.32em")),c!==k&&(N=N.transition(c),A=A.transition(c),T=T.transition(c),P=P.transition(c),E=E.transition(c).attr("opacity",f).attr("transform",(function(t){return isFinite(t=M(t))?y(t):this.getAttribute("transform")})),S.attr("opacity",f).attr("transform",(function(t){var n=this.parentNode.__axis;return y(n&&isFinite(n=n(t))?n:M(t))}))),E.remove(),N.attr("d",4===t||2==t?a?"M"+s*a+","+x+"H0.5V"+b+"H"+s*a:"M0.5,"+x+"V"+b:a?"M"+x+","+s*a+"V0.5H"+b+"V"+s*a:"M"+x+",0.5H"+b),A.attr("opacity",1).attr("transform",(function(t){return y(M(t))})),T.attr(l+"2",s*o),P.attr(l,s*_).text(d),k.filter(m).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",2===t?"start":4===t?"end":"middle"),k.each((function(){this.__axis=M;}));}return _.scale=function(t){return arguments.length?(n=t,_):n},_.ticks=function(){return e=c.call(arguments),_},_.tickArguments=function(t){return arguments.length?(e=null==t?[]:c.call(t),_):e.slice()},_.tickValues=function(t){return arguments.length?(r=null==t?null:c.call(t),_):r&&r.slice()},_.tickFormat=function(t){return arguments.length?(i=t,_):i},_.tickSize=function(t){return arguments.length?(o=a=+t,_):o},_.tickSizeInner=function(t){return arguments.length?(o=+t,_):o},_.tickSizeOuter=function(t){return arguments.length?(a=+t,_):a},_.tickPadding=function(t){return arguments.length?(u=+t,_):u},_}var _={value:function(){}};function w(){for(var t,n=0,e=arguments.length,r={};n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return {type:t,name:e}}))}function M(t,n){for(var e,r=0,i=t.length;r0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),A.hasOwnProperty(n)?{space:A[n],local:t}:t}function S(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===N&&n.documentElement.namespaceURI===N?n.createElement(t):n.createElementNS(e,t)}}function T(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function P(t){var n=E(t);return (n.local?T:S)(n)}function C(){}function q(t){return null==t?C:function(){return this.querySelector(t)}}function z(){return []}function L$2(t){return null==t?z:function(){return this.querySelectorAll(t)}}function j(t){return function(){return this.matches(t)}}function X(t){return new Array(t.length)}function O(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n;}O.prototype={constructor:O,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};function V(t,n,e,r,i,o){for(var a,u=0,s=n.length,l=o.length;un?1:t>=n?0:NaN}function D(t){return function(){this.removeAttribute(t);}}function $(t){return function(){this.removeAttributeNS(t.space,t.local);}}function H(t,n){return function(){this.setAttribute(t,n);}}function F(t,n){return function(){this.setAttributeNS(t.space,t.local,n);}}function Y(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e);}}function B(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e);}}function U(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function G(t){return function(){this.style.removeProperty(t);}}function Z(t,n,e){return function(){this.style.setProperty(t,n,e);}}function K(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e);}}function Q(t,n){return t.style.getPropertyValue(n)||U(t).getComputedStyle(t,null).getPropertyValue(n)}function J(t){return function(){delete this[t];}}function W(t,n){return function(){this[t]=n;}}function tt(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e;}}function nt(t){return t.trim().split(/^|\s+/)}function et(t){return t.classList||new rt(t)}function rt(t){this._node=t,this._names=nt(t.getAttribute("class")||"");}function it(t,n){for(var e=et(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")));},contains:function(t){return this._names.indexOf(t)>=0}};var xt={},bt=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(xt={mouseenter:"mouseover",mouseleave:"mouseout"}));function Mt(t,n,e){return t=kt(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n);}}function kt(t,n,e){return function(r){var i=bt;bt=r;try{t.call(this,this.__data__,n,e);}finally{bt=i;}}}function Nt(t){return t.trim().split(/^|\s+/).map((function(t){var n="",e=t.indexOf(".");return e>=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}function At(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=w&&(w=_+1);!(y=v[w])&&++w=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=I);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?G:"function"==typeof n?K:Z)(t,n,null==e?"":e)):Q(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?J:"function"==typeof n?tt:W)(t,n)):this.node()[t]},classed:function(t,n){var e=nt(t+"");if(arguments.length<2){for(var r=et(this.node()),i=-1,o=e.length;++i>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?vn(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?vn(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=on.exec(t))?new _n(n[1],n[2],n[3],1):(n=an.exec(t))?new _n(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=un.exec(t))?vn(n[1],n[2],n[3],n[4]):(n=sn.exec(t))?vn(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=ln.exec(t))?Mn(n[1],n[2]/100,n[3]/100,1):(n=cn.exec(t))?Mn(n[1],n[2]/100,n[3]/100,n[4]):hn.hasOwnProperty(t)?gn(hn[t]):"transparent"===t?new _n(NaN,NaN,NaN,0):null}function gn(t){return new _n(t>>16&255,t>>8&255,255&t,1)}function vn(t,n,e,r){return r<=0&&(t=n=e=NaN),new _n(t,n,e,r)}function mn(t){return t instanceof Qt||(t=dn(t)),t?new _n((t=t.rgb()).r,t.g,t.b,t.opacity):new _n}function yn(t,n,e,r){return 1===arguments.length?mn(t):new _n(t,n,e,null==r?1:r)}function _n(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r;}function wn(){return "#"+bn(this.r)+bn(this.g)+bn(this.b)}function xn(){var t=this.opacity;return (1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function bn(t){return ((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Mn(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Nn(t,n,e,r)}function kn(t){if(t instanceof Nn)return new Nn(t.h,t.s,t.l,t.opacity);if(t instanceof Qt||(t=dn(t)),!t)return new Nn;if(t instanceof Nn)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,s=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&s<1?0:a,new Nn(a,u,s,t.opacity)}function Nn(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r;}function An(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}function En(t){return function(){return t}}function Sn(t){return 1==(t=+t)?Tn:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):En(isNaN(n)?e:n)}}function Tn(t,n){var e=n-t;return e?function(t,n){return function(e){return t+e*n}}(t,e):En(isNaN(t)?n:t)}Zt(Qt,dn,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:fn,formatHex:fn,formatHsl:function(){return kn(this).formatHsl()},formatRgb:pn,toString:pn}),Zt(_n,yn,Kt(Qt,{brighter:function(t){return t=null==t?Wt:Math.pow(Wt,t),new _n(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?Jt:Math.pow(Jt,t),new _n(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return -.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:wn,formatHex:wn,formatRgb:xn,toString:xn})),Zt(Nn,(function(t,n,e,r){return 1===arguments.length?kn(t):new Nn(t,n,e,null==r?1:r)}),Kt(Qt,{brighter:function(t){return t=null==t?Wt:Math.pow(Wt,t),new Nn(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?Jt:Math.pow(Jt,t),new Nn(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new _n(An(t>=240?t-240:t+120,i,r),An(t,i,r),An(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return (0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return (1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var Pn=function t(n){var e=Sn(n);function r(t,n){var r=e((t=yn(t)).r,(n=yn(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=Tn(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function Cn(t,n){n||(n=[]);var e,r=t?Math.min(n.length,t.length):0,i=n.slice();return function(o){for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,s.push({i:a,x:Ln(e,r)})),o=On.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:Ln(t,n)})):n&&e.push(i(e)+"rotate("+n+r);}(o.rotate,a.rotate,u,s),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:Ln(t,n)}):n&&e.push(i(e)+"skewX("+n+r);}(o.skewX,a.skewX,u,s),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:Ln(t,e)},{i:u-2,x:Ln(n,r)});}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")");}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,s),o=a=null,function(t){for(var n,e=-1,r=s.length;++e=0&&n._call.call(null,t),n=n._next;--Wn;}();}finally{Wn=0,function(){var t,n,e=Zn,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Zn=n);Kn=t,pe(r);}(),re=0;}}function fe(){var t=oe.now(),n=t-ee;n>1e3&&(ie-=n,ee=t);}function pe(t){Wn||(te&&(te=clearTimeout(te)),t-re>24?(t<1/0&&(te=setTimeout(he,t-oe.now()-ie)),ne&&(ne=clearInterval(ne))):(ne||(ee=oe.now(),ne=setInterval(fe,1e3)),Wn=1,ae(he)));}function de(t,n,e){var r=new le;return n=null==n?0:+n,r.restart((function(e){r.stop(),t(e+n);}),n,e),r}le.prototype=ce.prototype={constructor:le,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?ue():+e)+(null==n?0:+n),this._next||Kn===this||(Kn?Kn._next=this:Zn=this,Kn=this),this._call=t,this._time=e,pe();},stop:function(){this._call&&(this._call=null,this._time=1/0,pe());}};var ge=w("start","end","cancel","interrupt"),ve=[];function me(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=1,e.timer.restart(a,e.delay,e.time),e.delay<=t&&a(t-e.delay);}function a(o){var l,c,h,f;if(1!==e.state)return s();for(l in i)if((f=i[l]).name===e.name){if(3===f.state)return de(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[l]):+l0)throw new Error("too late; already scheduled");return e}function _e(t,n){var e=we(t,n);if(e.state>3)throw new Error("too late; already running");return e}function we(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function xe(t,n){var e,r;return function(){var i=_e(this,t),o=i.tween;if(o!==e)for(var a=0,u=(r=e=o).length;a=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?ye:_e;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i;}}var De=Lt.prototype.constructor;function $e(t){return function(){this.style.removeProperty(t);}}function He(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e);}}function Fe(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&He(t,o,e)),r}return o._value=n,o}function Ye(t){return function(n){this.textContent=t.call(this,n);}}function Be(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&Ye(r)),n}return r._value=t,r}var Ue=0;function Ge(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r;}function Ze(){return ++Ue}var Ke=Lt.prototype;Ge.prototype={constructor:Ge,select:function(t){var n=this._name,e=this._id;"function"!=typeof t&&(t=q(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;a2&&e.state<5,e.state=6,e.timer.stop(),e.on.call(r?"interrupt":"cancel",t,t.__data__,e.index,e.group),delete o[i]):a=!1;a&&delete t.__transition;}}(this,t);}))},Lt.prototype.transition=function(t){var n,e;t instanceof Ge?(n=t._id,t=t._name):(n=Ze(),(e=Qe).time=ue(),t=null==t?null:t+"");for(var r=this._groups,i=r.length,o=0;onr)if(Math.abs(c*u-s*l)>nr&&i){var f=e-o,p=r-a,d=u*u+s*s,g=f*f+p*p,v=Math.sqrt(d),m=Math.sqrt(h),y=i*Math.tan((We-Math.acos((d+h-g)/(2*v*m)))/2),_=y/m,w=y/v;Math.abs(_-1)>nr&&(this._+="L"+(t+_*l)+","+(n+_*c)),this._+="A"+i+","+i+",0,0,"+ +(c*f>l*p)+","+(this._x1=t+w*u)+","+(this._y1=n+w*s);}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,o=!!o;var a=(e=+e)*Math.cos(r),u=e*Math.sin(r),s=t+a,l=n+u,c=1^o,h=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+s+","+l:(Math.abs(this._x1-s)>nr||Math.abs(this._y1-l)>nr)&&(this._+="L"+s+","+l),e&&(h<0&&(h=h%tr+tr),h>er?this._+="A"+e+","+e+",0,1,"+c+","+(t-a)+","+(n-u)+"A"+e+","+e+",0,1,"+c+","+(this._x1=s)+","+(this._y1=l):h>nr&&(this._+="A"+e+","+e+",0,"+ +(h>=We)+","+c+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))));},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z";},toString:function(){return this._}};var or="$";function ar(){}function ur(t,n){var e=new ar;if(t instanceof ar)t.each((function(t,n){e.set(n,t);}));else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==n)for(;++i1?r[0]+r.slice(2):r,+t.slice(e+1)]}function hr(t){return (t=cr(Math.abs(t)))?t[1]:NaN}sr.prototype={constructor:sr,has:lr.has,add:function(t){return this[or+(t+="")]=t,this},remove:lr.remove,clear:lr.clear,values:lr.keys,size:lr.size,empty:lr.empty,each:lr.each};var fr,pr=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function dr(t){if(!(n=pr.exec(t)))throw new Error("invalid format: "+t);var n;return new gr({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function gr(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+"";}function vr(t,n){var e=cr(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}dr.prototype=gr.prototype,gr.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var mr={"%":function(t,n){return (100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return vr(100*t,n)},r:vr,s:function(t,n){var e=cr(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(fr=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+cr(t,Math.max(0,n+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}};function yr(t){return t}var _r,wr,xr=Array.prototype.map,br=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function Mr(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?yr:(n=xr.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,u=n[0],s=0;i>0&&u>0&&(s+u+1>r&&(u=Math.max(1,r-s)),o.push(t.substring(i-=u,i+u)),!((s+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",u=void 0===t.numerals?yr:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(xr.call(t.numerals,String)),s=void 0===t.percent?"%":t.percent+"",l=void 0===t.minus?"-":t.minus+"",c=void 0===t.nan?"NaN":t.nan+"";function h(t){var n=(t=dr(t)).fill,e=t.align,h=t.sign,f=t.symbol,p=t.zero,d=t.width,g=t.comma,v=t.precision,m=t.trim,y=t.type;"n"===y?(g=!0,y="g"):mr[y]||(void 0===v&&(v=12),m=!0,y="g"),(p||"0"===n&&"="===e)&&(p=!0,n="0",e="=");var _="$"===f?i:"#"===f&&/[boxX]/.test(y)?"0"+y.toLowerCase():"",w="$"===f?o:/[%p]/.test(y)?s:"",x=mr[y],b=/[defgprs%]/.test(y);function M(t){var i,o,s,f=_,M=w;if("c"===y)M=x(t)+M,t="";else {var k=(t=+t)<0||1/t<0;if(t=isNaN(t)?c:x(Math.abs(t),v),m&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0);}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),k&&0==+t&&"+"!==h&&(k=!1),f=(k?"("===h?h:l:"-"===h||"("===h?"":h)+f,M=("s"===y?br[8+fr/3]:"")+M+(k&&"("===h?")":""),b)for(i=-1,o=t.length;++i(s=t.charCodeAt(i))||s>57){M=(46===s?a+t.slice(i+1):t.slice(i))+M,t=t.slice(0,i);break}}g&&!p&&(t=r(t,1/0));var N=f.length+t.length+M.length,A=N>1)+f+t+M+A.slice(N);break;default:t=A+f+t+M;}return u(t)}return v=void 0===v?6:/[gprs]/.test(y)?Math.max(1,Math.min(21,v)):Math.max(0,Math.min(20,v)),M.toString=function(){return t+""},M}return {format:h,formatPrefix:function(t,n){var e=h(((t=dr(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(hr(n)/3))),i=Math.pow(10,-r),o=br[8+r/3];return function(t){return e(i*t)+o}}}}function kr(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t);}return this}t.format=void 0,_r=Mr({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"}),t.format=_r.format,wr=_r.formatPrefix;var Nr=Array.prototype,Ar=Nr.map,Er=Nr.slice;function Sr(t){return +t}var Tr=[0,1];function Pr(t){return t}function Cr(t,n){return (n-=t=+t)?function(e){return (e-t)/n}:function(t){return function(){return t}}(isNaN(n)?NaN:.5)}function qr(t){var n,e=t[0],r=t[t.length-1];return e>r&&(n=e,e=r,r=n),function(t){return Math.max(e,Math.min(r,t))}}function zr(t,n,e){var r=t[0],i=t[1],o=n[0],a=n[1];return i2?Lr:zr,i=o=null,h}function h(n){return isNaN(n=+n)?e:(i||(i=r(a.map(t),u,s)))(t(l(n)))}return h.invert=function(e){return l(n((o||(o=r(u,a.map(t),Ln)))(e)))},h.domain=function(t){return arguments.length?(a=Ar.call(t,Sr),l===Pr||(l=qr(a)),c()):a.slice()},h.range=function(t){return arguments.length?(u=Er.call(t),c()):u.slice()},h.rangeRound=function(t){return u=Er.call(t),s=In,c()},h.clamp=function(t){return arguments.length?(l=t?qr(a):Pr,h):l!==Pr},h.interpolate=function(t){return arguments.length?(s=t,c()):s},h.unknown=function(t){return arguments.length?(e=t,h):e},function(e,r){return t=e,n=r,c()}}function Or(t,n){return Xr()(t,n)}function Vr(n,e,r,i){var s,l=function(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),s=r/i;return s>=o?i*=10:s>=a?i*=5:s>=u&&(i*=2),n0?r=l(u=Math.floor(u/r)*r,s=Math.ceil(s/r)*r,e):r<0&&(r=l(u=Math.ceil(u*r)/r,s=Math.floor(s*r)/r,e)),r>0?(i[o]=Math.floor(u/r)*r,i[a]=Math.ceil(s/r)*r,n(i)):r<0&&(i[o]=Math.ceil(u*r)/r,i[a]=Math.floor(s*r)/r,n(i)),t},t}function Ir(t){return Math.log(t)}function Dr(t){return Math.exp(t)}function $r(t){return -Math.log(-t)}function Hr(t){return -Math.exp(-t)}function Fr(t){return isFinite(t)?+("1e"+t):t<0?0:t}function Yr(t){return function(n){return -t(-n)}}function Br(n){var e,r,i=n(Ir,Dr),o=i.domain,a=10;function u(){return e=function(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),function(n){return Math.log(n)/t})}(a),r=function(t){return 10===t?Fr:t===Math.E?Math.exp:function(n){return Math.pow(t,n)}}(a),o()[0]<0?(e=Yr(e),r=Yr(r),n($r,Hr)):n(Ir,Dr),i}return i.base=function(t){return arguments.length?(a=+t,u()):a},i.domain=function(t){return arguments.length?(o(t),u()):o()},i.ticks=function(t){var n,i=o(),u=i[0],l=i[i.length-1];(n=l0){for(;pl)break;v.push(f);}}else for(;p=1;--h)if(!((f=c*h)l)break;v.push(f);}}else v=s(p,d,Math.min(d-p,g)).map(r);return n?v.reverse():v},i.tickFormat=function(n,o){if(null==o&&(o=10===a?".0e":","),"function"!=typeof o&&(o=t.format(o)),n===1/0)return o;null==n&&(n=10);var u=Math.max(1,a*n/i.ticks().length);return function(t){var n=t/r(Math.round(e(t)));return n*ah;}s.mouse("drag");}function g(){jt(bt.view).on("mousemove.drag mouseup.drag",null),function(t,n){var e=t.document.documentElement,r=jt(t).on("dragstart.drag",null);n&&(r.on("click.drag",Dt,!0),setTimeout((function(){r.on("click.drag",null);}),0)),"onselectstart"in e?r.on("selectstart.drag",null):(e.style.MozUserSelect=e.__noselect,delete e.__noselect);}(bt.view,e),Dt(),s.mouse("end");}function v(){if(i.apply(this,arguments)){var t,n,e=bt.changedTouches,r=o.apply(this,arguments),a=e.length;for(t=0;t plines[plines.length - 1]; i -= tickInterval) pticks.push(i);
-
- const altticks = [];
- for (let i = 0; i < 20000; i += (10000 / 3.28084)) altticks.push(atm.pressureFromElevation(i));
- //console.log(altticks);
-
- const barbsize = 15; /////
- // functions for Scales and axes. Note the inverted domain for the y-scale: bigger is up!
- const r = t.scaleLinear().range([0, 300]).domain([0, 150]);
- t.scaleLinear();
- const bisectTemp = t.bisector(function (d) { return d.press; }).left; // bisector function for tooltips
- let w, h, x, y, xAxis, yAxis, yAxis2, yAxis3;
- let ymax; //log scale for max top pressure
-
- let dataReversed = [];
- let dataAr = [];
- //aux
- const unitSpd = "kt"; // or kmh
- let unitAlt = "m";
- let windDisplay = "Barbs";
-
- if (isTouchDevice === void 0) {
- if (L && L.version) { //check if leaflet is loaded globally
- if (L.Browser.mobile) isTouchDevice = true;
- } else {
- isTouchDevice = ('ontouchstart' in window) ||
- (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
- }
- }
- //console.log("this is a touch device:", isTouchDevice);
-
-
-
- //containers
- const wrapper = outerWrapper.append("div").style("position","relative");
- const cloudContainer = wrapper.append("div").attr("class", "cloud-container");
- const svg = wrapper.append("svg").attr("class", "mainsvg"); //main svg
- const controls = wrapper.append("div").attr("class", "controls fnt controls1");
- const valuesContainer = wrapper.append("div").attr("class", "controls fnt controls2");
- const rangeContainer = wrapper.append("div").attr("class", "range-container fnt");
- const rangeContainer2 = wrapper.append("div").attr("class", "range-container-extra fnt");
- const container = svg.append("g");//.attr("id", "container"); //container
- const skewtbg = container.append("g").attr("class", "skewtbg");//.attr("id", "skewtbg");//background
- const skewtgroup = container.append("g").attr("class", "skewt"); // put skewt lines in this group (class skewt not used)
- const barbgroup = container.append("g").attr("class", "windbarb"); // put barbs in this group
- const tooltipgroup = container.append("g").attr("class", "tooltips"); //class tooltps not used
- const tooltipRect = container.append("rect").attr("class", "overlay");//.attr("id", "tooltipRect")
- const cloudCanvas1 = cloudContainer.append("canvas").attr("width", 1).attr("height", 200).attr("class", "cloud"); //original = width 10 and height 300
- this.cloudRef1 = cloudCanvas1.node();
- const cloudCanvas2 = cloudContainer.append("canvas").attr("width", 1).attr("height", 200).attr("class", "cloud");
- this.cloudRef2 = cloudCanvas2.node();
-
-
- function getFlags(f) {
- const flags = {
- "131072": "surface",
- "65536": "standard level",
- "32768": "tropopause level",
- "16384": "maximum wind level",
- "8192": "significant temperature level",
- "4096": "significant humidity level",
- "2048": "significant wind level",
- "1024": "beginning of missing temperature data",
- "512": "end of missing temperature data",
- "256": "beginning of missing humidity data",
- "128": "end of missing humidity data",
- "64": "beginning of missing wind data",
- "32": "end of missing wind data",
- "16": "top of wind sounding",
- "8": "level determined by regional decision",
- "4": "reserved",
- "2": "pressure level vertical coordinate"
- };
-
- const foundflags = [];
- const decode = (a, i) => {
- if (a % 2) foundflags.push(flags[1 << i]);
- if (a) decode(a >> 1, i + 1);
- };
- decode(f, 0);
- //console.log(foundflags);
- return foundflags;
- }
-
-
-
- //local functions
- function setVariables() {
- width = parseInt(wrapper.style('width'), 10);
- height = height || width;
- //if (height>width) height = width;
- w = width - margin.left - margin.right;
- h = height - margin.top - margin.bottom;
- tan = Math.tan((gradient || 55) * deg2rad);
- //use the h for the x range, so that appearance does not change when resizing, remains square
-
- ymax = t.scaleLog().range([0 ,h ]).domain([maxtopp, basep]);
- y = t.scaleLog().range([0 ,h ]).domain([topp, basep]);
-
- temprange = init_temprange * (h-ymax(topp))/ (h-ymax(maxtopp));
- x = t.scaleLinear().range([w/2 - h*2, w/2 + h*2]).domain([midtemp - temprange * 4, midtemp + temprange * 4]); //range is w*2
-
- xAxisTicks = temprange < 40 ? 30: 40;
- xAxis = t.axisBottom(x).tickSize(0, 0).ticks(xAxisTicks);//.orient("bottom");
- yAxis = t.axisLeft(y).tickSize(0, 0).tickValues(plines.filter(p => (p % 100 == 0 || p == 50 || p == 150))).tickFormat(t.format(".0d"));//.orient("left");
- yAxis2 = t.axisRight(y).tickSize(5, 0).tickValues(pticks);//.orient("right");
- yAxis3 = t.axisLeft(y).tickSize(2, 0).tickValues(altticks);
-
- steph = atm.getElevation(topp) / (h/12);
-
- }
-
- function convSpd(msvalue, unit) {
- switch (unit) {
- case "kt":
- return msvalue * 1.943844492;
- case "kmh":
- return msvalue * 3.6;
- default:
- return msvalue;
- }
- }
- function convAlt(v, unit) {
- switch (unit) {
- case "m":
- return Math.round(v) + unit;
- case "f":
- return Math.round(v * 3.28084) + "ft";
- default:
- return v;
- }
- }
-
- //assigns d3 events
- t.select(window).on('resize', resize);
-
- function resize() {
- skewtbg.selectAll("*").remove();
- setVariables();
- svg.attr("width", w + margin.right + margin.left).attr("height", h + margin.top + margin.bottom);
- container.attr("transform", "translate(" + margin.left + "," + (margin.top) + ")");
- drawBackground();
- dataAr.forEach(d => {
- plot(d.data, { add: true, select: false });
- });//redraw each plot
- if (selectedSkewt) selectSkewt(selectedSkewt.data);
- shiftXAxis();
- tooltipRect.attr("width", w).attr("height", h);
-
- cloudContainer.style("left", (margin.left + 2) + "px").style("top", margin.top + "px").style("height", h + "px");
- const canTop = y(100); //top of canvas for pressure 100
- cloudCanvas1.style("left", "0px").style("top", canTop + "px").style("height", (h - canTop) + "px");
- cloudCanvas2.style("left", "10px").style("top", canTop + "px").style("height", (h - canTop) + "px");
- }
-
- const lines = {};
- let clipper;
- let xAxisValues;
- //let tempLine, tempdewLine; now in object
-
-
- const drawBackground = function () {
-
- // Add clipping path
- clipper = skewtbg.append("clipPath")
- .attr("id", "clipper")
- .append("rect")
- .attr("x", 0)
- .attr("y", 0 )
- .attr("width", w)
- .attr("height", h );
-
- // Skewed temperature lines
- lines.temp = skewtbg.selectAll("templine")
- .data(t.scaleLinear().domain([midtemp - temprange * 4, midtemp + temprange*4]).ticks(xAxisTicks))
- .enter().append("line")
- .attr("x1", d => x(d) - 0.5 + (y(basep) - y(topp)) / tan)
- .attr("x2", d => x(d) - 0.5)
- .attr("y1", 0)
- .attr("y2", h)
- .attr("class", d => d == 0 ? `tempzero ${buttons["Temp"].hi ? "highlight-line" : ""}` : `templine ${buttons["Temp"].hi ? "highlight-line" : ""}`)
- .attr("clip-path", "url(#clipper)");
- //.attr("transform", "translate(0," + h + ") skewX(-30)");
-
-
- /*
- let topTempOffset = x.invert(h/tan + w/2);
- let elevDiff = (atm.getElevation(topp) - atm.getElevation(basep));// * 3.28084;
- let km11y = h*(11000 - atm.getElevation(basep)) / elevDiff;
- let tempOffset11 = x.invert(km11y/tan + w/2);
-
- console.log("top temp shift", tempOffset11, x.invert(km11y/tan) ) ;//(elevDiff/304.8)); //deg per 1000ft
- */
-
- const pp = moving ?
- [basep, basep - (basep - topp) * 0.25, basep - (basep - topp) * 0.5, basep - (basep - topp) * 0.75, topp]
- : t.range(basep, topp - 50, pIncrement);
-
-
- const pAt11km = atm.pressureFromElevation(11000);
- //console.log(pAt11km);
-
- const elrFx = t.line()
- .curve(t.curveLinear)
- .x(function (d, i) {
- atm.getElevation2(d);
- const t = d > pAt11km ? 15 - atm.getElevation(d) * 0.00649 : -56.5; //6.49 deg per 1000 m
- return x(t) + (y(basep) - y(d)) / tan;
- })
- .y(function (d, i) { return y(d) });
-
- lines.elr = skewtbg.selectAll("elr")
- .data([plines.filter(p => p > pAt11km).concat([pAt11km, 50])])
- .enter().append("path")
- .attr("d", elrFx)
- .attr("clip-path", "url(#clipper)")
- .attr("class", `elr ${showElr ? "highlight-line" : ""}`);
-
- // Logarithmic pressure lines
- lines.pressure = skewtbg.selectAll("pressureline")
- .data(plines)
- .enter().append("line")
- .attr("x1", - w)
- .attr("x2", 2 * w)
- .attr("y1", y)
- .attr("y2", y)
- .attr("clip-path", "url(#clipper)")
- .attr("class", `pressure ${buttons["Pressure"].hi ? "highlight-line" : ""}`);
-
- // create array to plot adiabats
-
- const dryad = t.scaleLinear().domain([midtemp - temprange * 2, midtemp + temprange * 6]).ticks(xAxisTicks);
-
- const all = [];
-
- for (let i = 0; i < dryad.length; i++) {
- const z = [];
- for (let j = 0; j < pp.length; j++) { z.push(dryad[i]); }
- all.push(z);
- }
-
-
- const drylineFx = t.line()
- .curve(t.curveLinear)
- .x(function (d, i) {
- return x(
- atm.dryLapse(pp[i], K0 + d, basep) - K0
- ) + (y(basep) - y(pp[i])) / tan;
- })
- .y(function (d, i) { return y(pp[i]) });
-
- // Draw dry adiabats
- lines.dryadiabat = skewtbg.selectAll("dryadiabatline")
- .data(all)
- .enter().append("path")
- .attr("class", `dryadiabat ${buttons["Dry Adiabat"].hi ? "highlight-line" : ""}`)
- .attr("clip-path", "url(#clipper)")
- .attr("d", drylineFx);
-
- // moist adiabat fx
- let temp;
- const moistlineFx = t.line()
- .curve(t.curveLinear)
- .x(function (d, i) {
- temp = i == 0 ? K0 + d : ((temp + atm.moistGradientT(pp[i], temp) * (moving ? (topp - basep) / 4 : pIncrement)));
- return x(temp - K0) + (y(basep) - y(pp[i])) / tan;
- })
- .y(function (d, i) { return y(pp[i]) });
-
- // Draw moist adiabats
- lines.moistadiabat = skewtbg.selectAll("moistadiabatline")
- .data(all)
- .enter().append("path")
- .attr("class", `moistadiabat ${buttons["Moist Adiabat"].hi ? "highlight-line" : ""}`)
- .attr("clip-path", "url(#clipper)")
- .attr("d", moistlineFx);
-
- // isohume fx
- let mixingRatio;
- const isohumeFx = t.line()
- .curve(t.curveLinear)
- .x(function (d, i) {
- //console.log(d);
- if (i == 0) mixingRatio = atm.mixingRatio(atm.saturationVaporPressure(d + K0), pp[i]);
- temp = atm.dewpoint(atm.vaporPressure(pp[i], mixingRatio));
- return x(temp - K0) + (y(basep) - y(pp[i])) / tan;
- })
- .y(function (d, i) { return y(pp[i]) });
-
- // Draw isohumes
- lines.isohume = skewtbg.selectAll("isohumeline")
- .data(all)
- .enter().append("path")
- .attr("class", `isohume ${buttons["Isohume"].hi ? "highlight-line" : ""}`)
- .attr("clip-path", "url(#clipper)")
- .attr("d", isohumeFx);
-
- // Line along right edge of plot
- skewtbg.append("line")
- .attr("x1", w - 0.5)
- .attr("x2", w - 0.5)
- .attr("y1", 0)
- .attr("y2", h)
- .attr("class", "gridline");
-
- // Add axes
- xAxisValues = skewtbg.append("g").attr("class", "x axis").attr("transform", "translate(0," + (h - 0.5 ) + ")").call(xAxis).attr("clip-path", "url(#clipper)");
- skewtbg.append("g").attr("class", "y axis").attr("transform", "translate(-0.5,0)").call(yAxis);
- skewtbg.append("g").attr("class", "y axis ticks").attr("transform", "translate(-0.5,0)").call(yAxis2);
- skewtbg.append("g").attr("class", "y axis hght-ticks").attr("transform", "translate(-0.5,0)").call(yAxis3);
- };
-
- const makeBarbTemplates = function () {
- const speeds = t.range(5, 205, 5);
- const barbdef = container.append('defs');
- speeds.forEach(function (d) {
- const thisbarb = barbdef.append('g').attr('id', 'barb' + d);
- const flags = Math.floor(d / 50);
- const pennants = Math.floor((d - flags * 50) / 10);
- const halfpennants = Math.floor((d - flags * 50 - pennants * 10) / 5);
- let px = barbsize / 2;
- // Draw wind barb stems
- thisbarb.append("line").attr("x1", 0).attr("x2", 0).attr("y1", -barbsize / 2).attr("y2", barbsize / 2);
- // Draw wind barb flags and pennants for each stem
- for (var i = 0; i < flags; i++) {
- thisbarb.append("polyline")
- .attr("points", "0," + px + " -6," + (px) + " 0," + (px - 2))
- .attr("class", "flag");
- px -= 5;
- }
- // Draw pennants on each barb
- for (i = 0; i < pennants; i++) {
- thisbarb.append("line")
- .attr("x1", 0)
- .attr("x2", -6)
- .attr("y1", px)
- .attr("y2", px + 2);
- px -= 3;
- }
- // Draw half-pennants on each barb
- for (i = 0; i < halfpennants; i++) {
- thisbarb.append("line")
- .attr("x1", 0)
- .attr("x2", -3)
- .attr("y1", px)
- .attr("y2", px + 1);
- px -= 3;
- }
- });
- };
-
-
- const shiftXAxis = function () {
- clipper.attr("x", -xOffset);
- xAxisValues.attr("transform", `translate(${xOffset}, ${h - 0.5} )`);
- for (const p in lines) {
- lines[p].attr("transform", `translate(${xOffset},0)`);
- } dataAr.forEach(d => {
- for (const p in d.lines) {
- d.lines[p].attr("transform", `translate(${xOffset},0)`);
- }
- });
- };
-
-
- const drawToolTips = function () {
-
- // Draw tooltips
- const tmpcfocus = tooltipgroup.append("g").attr("class", "focus tmpc");
- tmpcfocus.append("circle").attr("r", 4);
- tmpcfocus.append("text").attr("x", 9).attr("dy", ".35em");
-
- const dwpcfocus = tooltipgroup.append("g").attr("class", "focus dwpc");
- dwpcfocus.append("circle").attr("r", 4);
- dwpcfocus.append("text").attr("x", -9).attr("text-anchor", "end").attr("dy", ".35em");
-
- const hghtfocus = tooltipgroup.append("g").attr("class", "focus");
- const hght1 = hghtfocus.append("text").attr("x", "0.8em").attr("text-anchor", "start").attr("dy", ".35em");
- const hght2 = hghtfocus.append("text").attr("x", "0.8em").attr("text-anchor", "start").attr("dy", "-0.65em").style("fill", "blue");
-
- const wspdfocus = tooltipgroup.append("g").attr("class", "focus windspeed");
- const wspd1 = wspdfocus.append("text").attr("x", "0.8em").attr("text-anchor", "start").attr("dy", ".35em");
- const wspd2 = wspdfocus.append("text").attr("x", "0.8em").attr("text-anchor", "start").attr("dy", "-0.65em").style("fill", "red");
- const wspd3 = wspdfocus.append("text").attr("class", "skewt-wind-arrow").html("⇩");
- const wspd4 = wspdfocus.append("text").attr("y", "1em").attr("text-anchor", "start").style("fill", "rgba(0,0,0,0.3)").style("font-size", "10px");
- //console.log(wspdfocus)
-
- let startX = null;
-
-
- function start(e) {
- showTooltips();
- move.call(tooltipRect.node());
- startX = t.mouse(this)[0] - xOffset;
- }
-
- function end(e) {
- startX = null;
- }
-
- const hideTooltips = () => {
- [tmpcfocus, dwpcfocus, hghtfocus, wspdfocus].forEach(e => e.style("display", "none"));
- currentY = null;
- };
- hideTooltips();
-
- const showTooltips = () => {
- [tmpcfocus, dwpcfocus, hghtfocus, wspdfocus].forEach(e => e.style("display", null));
- };
-
- const move2P = (y0) => {
- //console.log("mving to", y0);
- if (y0 || y0===0) showTooltips();
- const i = bisectTemp(dataReversed, y0, 1, dataReversed.length - 1);
- const d0 = dataReversed[i - 1];
- const d1 = dataReversed[i];
- const d = y0 - d0.press > d1.press - y0 ? d1 : d0;
- currentY = y0;
-
- tmpcfocus.attr("transform", "translate(" + (xOffset + x(d.temp) + (y(basep) - y(d.press)) / tan) + "," + y(d.press) + ")");
- dwpcfocus.attr("transform", "translate(" + (xOffset + x(d.dwpt) + (y(basep) - y(d.press)) / tan) + "," + y(d.press) + ")");
-
- hghtfocus.attr("transform", "translate(0," + y(d.press) + ")");
- hght1.html(" " + ((d.hght || d.hght===0) ? convAlt(d.hght, unitAlt):"") ); //hgt or hghtagl ???
- hght2.html(" " + Math.round(d.dwpt) + "°C");
-
- wspdfocus.attr("transform", "translate(" + (w - (windDisplay=="Barbs" ? 70:80)) + "," + y(d.press) + ")");
- wspd1.html(isNaN(d.wspd) ? "" : (Math.round(convSpd(d.wspd, unitSpd) * 10) / 10 + unitSpd));
- wspd2.html(Math.round(d.temp) + "°C");
- wspd3.style("transform", `rotate(${d.wdir}deg)`);
- wspd4.html(d.flags ? getFlags(d.flags).map(f => `${f}`).join() : "");
- //console.log( getFlags(d.flags).join("
"));
-
- if (pressCbfs) pressCbfs.forEach(cbf=>cbf(d.press));
- };
-
- function move(e) {
- const newX = t.mouse(this)[0];
- if (startX !== null) {
- xOffset = -(startX - newX);
- shiftXAxis();
- }
- const y0 = y.invert(t.mouse(this)[1]); // get y value of mouse pointer in pressure space
- move2P(y0);
- }
-
- tooltipRect
- .attr("width", w)
- .attr("height", h);
-
- //.on("mouseover", start)
- //.on("mouseout", end)
- //.on("mousemove", move)
- if (!isTouchDevice) {
-
- tooltipRect.call(t.drag().on("start", start).on("drag", move).on("end", end));
- } else {
- tooltipRect
- //tooltipRect.node().addEventListener('touchstart',start, true)
- //tooltipRect.node().addEventListener('touchmove',move, true)
- //tooltipRect.node().addEventListener('touchend',end, true)
- .on('touchstart', start)
- .on('touchmove', move)
- .on('touchend', end);
- }
-
- Object.assign(this, { move2P, hideTooltips, showTooltips });
- };
-
-
-
- const drawParcelTraj = function (dataObj) {
-
- const { data, parctemp } = dataObj;
-
- if (data[0].dwpt == undefined) return;
-
- const pt = atm.parcelTrajectory(
- { level: data.map(e => e.press), gh: data.map(e => e.hght), temp: data.map(e => e.temp + K0) },
- moving ? 10 : xAxisTicks,
- parctemp + K0,
- data[0].press,
- data[0].dwpt + K0
- );
-
- //draw lines
- const parctrajFx = t.line()
- .curve(t.curveLinear)
- .x(function (d, i) { return x(d.t) + (y(basep) - y(d.p)) / tan; })
- .y(function (d, i) { return y(d.p); });
-
- //let parcLines={dry:[], moist:[], isohumeToDry:[], isohumeToTemp:[], moistFromCCL:[], TCONline:[], thrm:[], cloud:[]};
-
- const parcLines = { parcel: [], LCL: [], CCL: [], TCON: [], "THRM top": [], "CLD top": [] };
-
- for (const prop in parcLines) {
- const p = prop;
- if (dataObj.lines[p]) dataObj.lines[p].remove();
-
- let line = [], press;
- switch (p) {
- case "parcel":
- if (pt.dry) line.push(pt.dry);
- if (pt.moist) line.push(pt.moist);
- break;
- case "TCON":
- const t = pt.TCON;
- line = t !== void 0 ? [[[t, basep], [t, topp]]] : [];
- break;
- case "LCL":
- if (pt.isohumeToDry) line.push(pt.isohumeToDry);
- break;
- case "CCL":
- if (pt.isohumeToTemp) line.push(pt.isohumeToTemp);
- if (pt.moistFromCCL) line.push(pt.moistFromCCL);
- break;
- case "THRM top":
- press = pt.pThermalTop;
- if (press) line = [[[0, press], [400, press]]];
- break;
- case "CLD top":
- press = pt.pCloudTop;
- if (press) line = [[[0, press], [400, press]]];
- break;
- }
-
- if (line) parcLines[p] = line.map(e => e.map(ee => { return { t: ee[0] - K0, p: ee[1] } }));
-
- dataObj.lines[p] = skewtgroup
- .selectAll(p)
- .data(parcLines[p]).enter().append("path")
- .attr("class", `${p == "parcel" ? "parcel" : "cond-level"} ${selectedSkewt && data == selectedSkewt.data && (p == "parcel" || values[p].hi) ? "highlight-line" : ""}`)
- .attr("clip-path", "url(#clipper)")
- .attr("d", parctrajFx)
- .attr("transform", `translate(${xOffset},0)`);
- }
-
- //update values
- for (const p in values) {
- let v = pt[p == "CLD top" ? "cloudTop" : p == "THRM top" ? "elevThermalTop" : p];
- let CLDtopHi;
- if (p == "CLD top" && v == 100000) {
- v = data[data.length - 1].hght;
- CLDtopHi = true;
- }
- const txt = `${(p[0].toUpperCase() + p.slice(1)).replace(" ", " ")}:
${!v ? "" : p == "TCON" ? (v - K0).toFixed(1) + "°C" : (CLDtopHi ? "> " : "") + convAlt(v, unitAlt)}`;
- values[p].val.html(txt);
- }
- };
-
- const selectSkewt = function (data) { //use the data, then can be found from the outside by using data obj ref
- dataAr.forEach(d => {
- const found = d.data == data;
- for (const p in d.lines) {
- d.lines[p].classed("highlight-line", found && (!values[p] || values[p].hi));
- }
- if (found) {
- selectedSkewt = d;
- dataReversed = [].concat(d.data).reverse();
- ranges.parctemp.input.node().value = ranges.parctemp.value = d.parctemp = Math.round(d.parctemp * 10) / 10;
- ranges.parctemp.valueDiv.html(html4range(d.parctemp, "parctemp"));
- }
- });
- _this.hideTooltips();
- };
-
-
-
- //if in options: add, add new plot,
- //if select, set selected ix and highlight. if select false, must hightlight separtely.
- //ixShift used to shift to the right, used when you want to keep position 0 open.
- //max is the max number of plots, by default at the moment 2,
- const plot = function (s, { add, select, ixShift = 0, max = 2 } = {}) {
-
- if (s.length == 0) return;
-
- let ix = 0; //index of the plot, there may be more than one, to shift barbs and make clouds on canvas
-
- if (!add) {
- dataAr.forEach(d => { //clear all plots
- for (const p in d.lines) d.lines[p].remove();
- });
- dataAr = [];
- [1, 2].forEach(c => {
- const ctx = _this["cloudRef" + c].getContext("2d");
- ctx.clearRect(0, 0, 10, 200);
- });
- }
-
- let dataObj = dataAr.find(d => d.data == s);
-
- let data;
-
- if (!dataObj) {
- const parctemp = Math.round((s[0].temp + ranges.parctempShift.value) * 10) / 10;
- data = s; //do not filter here, filter creates new obj, looses ref
- //however, object itself can be changed.
- for(let i = 0; i=0 && data[i].rh<=100)){
- let {rh, temp} = data[i];
- data[i].dwpt = 243.04*(Math.log(rh/100)+((17.625*temp)/(243.04+temp)))/(17.625-Math.log(rh/100)-((17.625*temp)/(243.04+temp)));
- }
- }
- ix = dataAr.push({ data, parctemp, lines: {} }) - 1;
- dataObj = dataAr[ix];
- if (ix >= max) {
- console.log("more than max plots added");
- ix--;
- setTimeout((ix) => {
- if (dataAr.length > max) _this.removePlot(dataAr[ix].data);
- }, 1000, ix);
- }
- } else {
- ix = dataAr.indexOf(dataObj);
- data = dataObj.data;
- for (const p in dataObj.lines) dataObj.lines[p].remove();
- }
-
- //reset parctemp range if this is the selected range
- if (select) {
- ranges.parctemp.input.node().value = ranges.parctemp.value = dataObj.parctemp;
- ranges.parctemp.valueDiv.html(html4range(dataObj.parctemp, "parctemp"));
- }
-
- //skew-t stuff
-
- // Filter data, depending on range moving, or nullish values
-
- let data4moving;
- if (data.length > 50 && moving) {
- let prev = -1;
- data4moving = data.filter((e, i, a) => {
- const n = Math.floor(i * 50 / (a.length - 1));
- if (n > prev) {
- prev = n;
- return true;
- }
- });
- } else {
- data4moving = data.map(e=>e);
- }
- let data4temp = [data4moving.filter(e=>( e.temp || e.temp===0 ) && e.temp>-999 )];
- let data4dwpt = [data4moving.filter(e=>( e.dwpt || e.dwpt===0 ) && e.dwpt>-999 )];
-
-
-
-
-
- const templineFx = t.line().curve(t.curveLinear).x(function (d, i) { return x(d.temp) + (y(basep) - y(d.press)) / tan; }).y(function (d, i) { return y(d.press); });
- dataObj.lines.tempLine = skewtgroup
- .selectAll("templines")
- .data(data4temp).enter().append("path")
- .attr("class", "temp")//(d,i)=> `temp ${i<10?"skline":"mean"}` )
- .attr("clip-path", "url(#clipper)")
- .attr("d", templineFx);
-
- const tempdewlineFx = t.line().curve(t.curveLinear).x(function (d, i) { return x(d.dwpt) + (y(basep) - y(d.press)) / tan; }).y(function (d, i) { return y(d.press); });
- dataObj.lines.tempdewLine = skewtgroup
- .selectAll("tempdewlines")
- .data(data4dwpt).enter().append("path")
- .attr("class", "dwpt")//(d,i)=>`dwpt ${i<10?"skline":"mean"}` )
- .attr("clip-path", "url(#clipper)")
- .attr("d", tempdewlineFx);
-
- drawParcelTraj(dataObj);
-
-
-
- const siglines = data
- .filter((d, i, a, f) => d.flags && (f = getFlags(d.flags), f.includes("tropopause level") || f.includes("surface")) ? d.press : false)
- .map((d, i, a, f) => (f = getFlags(d.flags), { press: d.press, classes: f.map(e => e.replace(/ /g, "-")).join(" ") }));
-
- dataObj.lines.siglines = skewtbg.selectAll("siglines")
- .data(siglines)
- .enter().append("line")
- .attr("x1", - w).attr("x2", 2 * w)
- .attr("y1", d => y(d.press)).attr("y2", d => y(d.press))
- .attr("clip-path", "url(#clipper)")
- .attr("class", d => `sigline ${d.classes}`);
-
-
- //barbs stuff
-
- let lastH = -300;
- //filter barbs to be valid and not too crowded
- const barbs = data4moving.filter(function (d) {
- if (d.hght > lastH + steph && (d.wspd || d.wspd === 0) && d.press >= topp && !(d.wspd === 0 && d.wdir === 0)) lastH = d.hght;
- return d.hght == lastH;
- });
-
- dataObj.lines.barbs = barbgroup.append("svg").attr("class", `barblines ${windDisplay=="Numerical"?"hidden":""}`);//.attr("transform","translate(30,80)");
- dataObj.lines.barbs.selectAll("barbs")
- .data(barbs).enter().append("use")
- .attr("href", function (d) { return "#barb" + Math.round(convSpd(d.wspd, "kt") / 5) * 5; }) // 0,5,10,15,... always in kt
- .attr("transform", function (d) { return "translate(" + (w + 15 * (ix + ixShift)) + "," + y(d.press) + ") rotate(" + (d.wdir + 180) + ")"; });
-
-
- dataObj.lines.windtext = barbgroup.append("svg").attr("class", `windtext ${windDisplay=="Barbs"?"hidden":""}`);//.attr("class", "barblines");
- dataObj.lines.windtext.selectAll("windtext")
- .data(barbs).enter().append("g")
- .attr("transform",d=> `translate(${w + 28 * (ix + ixShift) - 20} , ${y(d.press)})`);
- dataObj.lines.windtext.selectAll("g").append("text")
- .html( "↑" )
- .style("transform",d=> "rotate(" + (180 + d.wdir)+"deg)");
- dataObj.lines.windtext.selectAll("g").append("text")
- .html( d=>Math.round(convSpd(d.wspd,"kt")))
- .attr("x","0.5em");
-
- ////clouds
- const clouddata = clouds.computeClouds(data);
- clouddata.canvas = _this["cloudRef" + (ix + ixShift + 1)];
- clouds.cloudsToCanvas(clouddata);
- dataObj.cloudCanvas = clouddata.canvas;
- //////
-
- if (select || dataAr.length == 1) {
- selectSkewt(dataObj.data);
- }
- shiftXAxis();
-
- return dataAr.length;
- };
-
-
- //// controls at bottom
-
- var buttons = { "Dry Adiabat": {}, "Moist Adiabat": {}, "Isohume": {}, "Temp": {}, "Pressure": {} };
- for (const p in buttons) {
- const b = buttons[p];
- b.hi = false;
- b.el = controls.append("div").attr("class", "buttons").text(p).on("click", () => {
- b.hi = !b.hi;
- b.el.node().classList[b.hi ? "add" : "remove"]("clicked");
- const line = p.replace(" ", "").toLowerCase();
- lines[line]._groups[0].forEach(p => p.classList[b.hi ? "add" : "remove"]("highlight-line"));
- });
- } this.refs.highlightButtons = controls.node();
-
- //values
- const values = {
- "surface": {},
- "LCL": { hi: true },
- "CCL": { hi: true },
- "TCON": { hi: false },
- "THRM top": { hi: false },
- "CLD top": { hi: false }
- };
-
- for (const prop in values) {
- const p = prop;
- const b = values[p];
- b.val = valuesContainer.append("div").attr("class", `buttons ${p == "surface" ? "noclick" : ""} ${b.hi ? "clicked" : ""}`).html(p + ":");
- if (/CCL|LCL|TCON|THRM top|CLD top/.test(p)) {
- b.val.on("click", () => {
- b.hi = !b.hi;
- b.val.node().classList[b.hi ? "add" : "remove"]("clicked");
- selectedSkewt.lines[p]._groups[0].forEach(p => p.classList[b.hi ? "add" : "remove"]("highlight-line"));
- });
- }
- }
- this.refs.valueButtons = valuesContainer.node();
-
- const ranges = {
- parctemp: { value: 10, step: 0.1, min: -50, max: 50 },
- topp: { min: 50, max: 900, step: 25, value: topp },
- parctempShift: { min: -5, step: 0.1, max: 10, value: parctempShift },
- gradient: { min: 0, max: 85, step: 1, value: gradient },
- // midtemp:{value:0, step:2, min:-50, max:50},
-
- };
-
- const unit4range = p => p == "gradient" ? "°" : p == "topp" ? "hPa" : "°C";
-
- const html4range = (v, p) => {
- let html = "";
- if (p == "parctempShift" && r.value >= 0) html += "+";
- html += (p == "gradient" || p == "topp" ? Math.round(v) : Math.round(v * 10) / 10) + unit4range(p);
- if (p == "parctemp") {
- const shift = selectedSkewt ? (Math.round((v - selectedSkewt.data[0].temp) * 10) / 10) : parctempShift;
- html += " " + (shift > 0 ? "+" : "") + shift + "";
- }
- return html;
- };
-
- for (const prop in ranges) {
- const p = prop;
- const contnr = p == "parctemp" || p == "topp" ? rangeContainer : rangeContainer2;
- const r = ranges[p];
- r.row=contnr.append("div").attr("class","row"); this.refs[p]=r.row.node();
- r.valueDiv = r.row.append("div").attr("class", "skewt-range-des").html(p == "gradient" ? "Gradient:" : p == "topp" ? "Top P:" : p == "parctemp" ? "Parcel T:" : "Parcel T Shift:");
- r.valueDiv = r.row.append("div").attr("class", "skewt-range-val").html(html4range(r.value, p));
- r.input = r.row.append("input").attr("type", "range").attr("min", r.min).attr("max", r.max).attr("step", r.step).attr("value", p == "gradient" ? 90 - r.value : r.value).attr("class", "skewt-ranges")
- .on("input", (a, b, c) => {
-
- _this.hideTooltips();
- r.value = +c[0].value;
-
- if (p == "gradient") {
- gradient = r.value = 90 - r.value;
- showErlFor2Sec(0, 0, r.input);
- //console.log("GRADIENT ST", gradient);
- }
- if (p == "topp") {
- showErlFor2Sec(0, 0, r.input);
- const h_oldtopp = y(basep) - y(topp);
- topp = r.value;
- const h_newtopp = y(basep) - y(topp);
- pIncrement = topp > 500 ? -25 : -50;
- if (adjustGradient) {
- ranges.gradient.value = gradient = Math.atan(Math.tan(gradient * deg2rad) * h_oldtopp / h_newtopp) / deg2rad;
- ranges.gradient.input.node().value = 90 - gradient; //will trigger input event anyway
- ranges.gradient.valueDiv.html(html4range(gradient, "gradient"));
- init_temprange*=h_oldtopp/h_newtopp;
- if (ranges.gradient.cbfs) ranges.gradient.cbfs.forEach(cbf => cbf(gradient));
- }
- steph = atm.getElevation(topp) / 30;
- }
- if (p == "parctempShift") {
- parctempShift = r.value;
- }
-
- r.valueDiv.html(html4range(r.value, p));
-
- clearTimeout(moving);
- moving = setTimeout(() => {
- moving = false;
- if (p == "parctemp") {
- if (selectedSkewt) drawParcelTraj(selectedSkewt); //value already set
- } else {
- resize();
- }
- }, 1000);
-
- if (p == "parctemp"){
- if (selectedSkewt) {
- selectedSkewt.parctemp = r.value;
- drawParcelTraj(selectedSkewt);
- }
- } else {
- resize();
- }
-
- //this.cbfRange({ topp, gradient, parctempShift });
- if (r.cbfs) r.cbfs.forEach(cbf => cbf(p=="gradient"? gradient: r.value));
- });
-
- //contnr.append("div").attr("class", "flex-break");
- }
-
-
- let showElr;
- const showErlFor2Sec = (a, b, target) => {
- target = target[0] || target.node();
- lines.elr.classed("highlight-line", true);
- clearTimeout(showElr);
- showElr = null;
- showElr = setTimeout(() => {
- target.blur();
- lines.elr.classed("highlight-line", showElr = null); //background may be drawn again
- }, 1000);
- };
-
- ranges.gradient.input.on("focus", showErlFor2Sec);
- ranges.topp.input.on("focus", showErlFor2Sec);
-
- const cbSpan = rangeContainer2.append("span").attr("class", "row checkbox-container");
- this.refs.maintainXCheckBox = cbSpan.node();
- cbSpan.append("input").attr("type", "checkbox").on("click", (a, b, e) => {
- adjustGradient = e[0].checked;
- });
- cbSpan.append("span").attr("class", "skewt-checkbox-text").html("Maintain temp range on X-axis when zooming");
-
- const selectUnits = rangeContainer2.append("div").attr("class", "row select-units");
- this.refs.selectUnits = selectUnits.node();
- selectUnits.append("div").style("width","10em").html("Select alt units: ");
- const units = { "meter": {}, "feet": {} };
- for (const prop in units) {
- const p = prop;
- units[p].hi = p[0] == unitAlt;
- units[p].el = selectUnits.append("div").attr("class", "buttons units" + (unitAlt == p[0] ? " clicked" : "")).text(p).on("click", () => {
- for (const p2 in units) {
- units[p2].hi = p == p2;
- units[p2].el.node().classList[units[p2].hi ? "add" : "remove"]("clicked");
- }
- unitAlt = p[0];
- if (currentY !== null) _this.move2P(currentY);
- drawParcelTraj(selectedSkewt);
- });
- }
- const selectWindDisp = rangeContainer2.append("div").attr("class", "row select-units");
- this.refs.selectWindDisp = selectWindDisp.node();
- selectWindDisp.append("div").style("width","10em").html("Select wind display: ");
- const windDisp = { "Barbs": {}, "Numerical": {} };
- for (const prop in windDisp) {
- const p = prop;
- windDisp[p].hi = p == windDisplay;
- windDisp[p].el = selectWindDisp.append("div").attr("class", "buttons units" + (windDisplay == p ? " clicked" : "")).text(p).on("click", () => {
- for (const p2 in windDisp) {
- windDisp[p2].hi = p == p2;
- windDisp[p2].el.node().classList[windDisp[p2].hi ? "add" : "remove"]("clicked");
- }
- windDisplay = p;
- //console.log(windDisplay);
- dataAr.forEach(d=>{
- d.lines.barbs.classed("hidden", windDisplay=="Numerical");
- d.lines.windtext.classed("hidden", windDisplay=="Barbs");
- });
- });
- }
- const removePlot = (s) => { //remove single plot
- const dataObj = dataAr.find(d => d.data == s);
- //console.log(dataObj);
- if (!dataObj) return;
- let ix=dataAr.indexOf(dataObj);
- //clear cloud canvas.
- if (dataObj.cloudCanvas){
- const ctx = dataObj.cloudCanvas.getContext("2d");
- ctx.clearRect(0, 0, 10, 200);
- }
-
- for (const p in dataObj.lines) {
- dataObj.lines[p].remove();
- }
- dataAr.splice(ix, 1);
- if(dataAr.length==0) {
- _this.hideTooltips();
- console.log("All plots removed");
- }
- };
-
- const clear = () => { //remove all plots and data
- dataAr.forEach(d => {
- for (const p in d.lines) d.lines[p].remove();
- const ctx = d.cloudCanvas.getContext("2d");
- ctx.clearRect(0, 0, 10, 200);
- });
- _this.hideTooltips();
- // these maybe not required, addressed by above.
- skewtgroup.selectAll("lines").remove();
- skewtgroup.selectAll("path").remove(); //clear previous paths from skew
- skewtgroup.selectAll("g").remove();
- barbgroup.selectAll("use").remove(); //clear previous paths from barbs
- dataAr = [];
- //if(tooltipRect)tooltipRect.remove(); tooltip rect is permanent
- };
-
- const clearBg = () => {
- skewtbg.selectAll("*").remove();
- };
-
- const setParams = (p) => {
- ({ height=height, topp=topp, parctempShift=parctempShift, parctemp=parctemp, basep=basep, steph=steph, gradient=gradient } = p);
- if (p=="gradient") ranges.gradient.input.value = 90 - p;
- else if (ranges[p]) ranges[p].input.value = p;
- //resize();
- };
-
- const getParams = () =>{
- return {height, topp, basep, steph, gradient, parctempShift, parctemp: selectSkewt.parctemp }
- };
-
- const shiftDegrees = function (d) {
- xOffset = x(0) - x(d) ;
- //console.log("xOffs", xOffset);
- shiftXAxis();
- };
-
-
- // Event cbfs.
- // possible events: temp, press, parctemp, topp, parctempShift, gradient;
-
- const pressCbfs=[];
- const tempCbfs=[];
- const on = (ev, cbf) =>{
- let evAr;
- if (ev=="press" || ev=="temp") {
- evAr=ev=="press"?pressCbfs:tempCbfs;
- } else {
- for (let p in ranges) {
- if(ev.toLowerCase() == p.toLowerCase()){
- if (!ranges[p].cbfs) ranges[p].cbfs = [];
- evAr=ranges[p].cbfs;
- }
- }
- }
- if (evAr){
- if (!evAr.includes(cbf)) {
- evAr.push(cbf);
- } else {
- console.log("EVENT ALREADY REGISTERED");
- }
- } else {
- console.log("EVENT NOT RECOGNIZED");
- }
- };
-
- const off = (ev, cbf) => {
- let evAr;
- if (ev=="press" || ev=="temp") {
- evAr=ev=="press"?pressCbfs:tempCbfs;
- } else {
- for (let p in ranges) {
- if(ranges[p].cbfs && ev.toLowerCase() == p.toLowerCase()){
- evAr=ranges[p].cbfs;
- }
- }
- }
- if (evAr) {
- let ix = evAr.findIndex(c=>cbf==c);
- if (ix>=0) evAr.splice(ix,1);
- }
- };
-
- // Add functions as public methods
-
- this.drawBackground = drawBackground;
- this.resize = resize;
- this.plot = plot;
- this.clear = clear; //clear all the plots
- this.clearBg = clearBg;
- this.selectSkewt = selectSkewt;
- this.removePlot = removePlot; //remove a specific plot, referenced by data object passed initially
-
- this.on = on;
- this.off = off;
- this.setParams = setParams;
- this.getParams = getParams;
- this.shiftDegrees = shiftDegrees;
-
- /**
- * parcelTrajectory:
- * @param params = {temp, gh, level},
- * @param {number} steps,
- * @param surfacetemp, surf pressure and surf dewpoint
- */
- this.parcelTrajectory = atm.parcelTrajectory;
-
- this.pressure2y = y;
- this.temp2x = x;
- this.gradient = gradient; //read only, use setParams to set.
-
- // this.move2P, this.hideTooltips, this.showTooltips, has been declared
-
- // this.cloudRef1 and this.cloudRef2 = references to the canvas elements to add clouds with other program
-
- this.refs.tooltipRect = tooltipRect.node();
-
- /* other refs:
- highlightButtons
- valueButtons
- parctemp
- topp
- gradient
- parctempShift
- maintainXCheckBox
- selectUnits
- selectWindDisp
- tooltipRect
- */
-
- //init
- setVariables();
- resize();
- drawToolTips.call(this); //only once
- makeBarbTemplates(); //only once
-};}());
\ No newline at end of file
diff --git a/js/tracker.js b/js/tracker.js
index cda3a47..e523437 100644
--- a/js/tracker.js
+++ b/js/tracker.js
@@ -27,8 +27,6 @@ var stationMarkerLookup = {};
var markerStationLookup = {};
var predictionAjax = [];
-var skewtdata = [];
-
var focusID = 0;
var receiverCanvas = null;
@@ -1375,7 +1373,7 @@ function updateVehicleInfo(vcallsign, newPosition) {
'
' +
'Path' +
((vehicle.vehicle_type!="car") ? 'Share' : '') +
- ((vehicle.vehicle_type!="car") ? 'SkewT' : '') +
+ ((vehicle.vehicle_type!="car" && newPosition.gps_alt > 1000 && vehicle.ascent_rate < 1) ? 'Hysplit' : '') +
'' +
'
';
//mobile
@@ -1387,7 +1385,7 @@ function updateVehicleInfo(vcallsign, newPosition) {
'
' +
'Path' +
((vehicle.vehicle_type!="car") ? 'Share' : '') +
- ((vehicle.vehicle_type!="car") ? 'SkewT' : '') +
+ ((vehicle.vehicle_type!="car" && newPosition.gps_alt > 1000 && vehicle.ascent_rate < 1) ? 'Hysplit' : '') +
'' +
'
';
var b = '
' +
@@ -1441,204 +1439,45 @@ function updateVehicleInfo(vcallsign, newPosition) {
return true;
}
-function skewTdelete () {
- var box = $("#skewtbox");
-
- skewt.clear();
- $('#resetSkewt').hide();
- $('#deleteSkewt').hide();
- $("#skewtSerial").text("Select a Radiosonde from the list and click 'SkewT' to plot. Note that not all radiosonde types are supported.");
- box.hide();
- //$('.skewt').hide();
- $("#skewt-plot").empty();
- checkSize();
+function generateHysplit(callsign) {
+ var vehicle = vehicles[callsign];
+ for (var alt = -1000; alt <= 1000; alt+=100) {
+ createHysplit(callsign, alt);
+ }
}
-function skewTrefresh () {
- skewt.clear();
- $("#skewt-plot").empty();
- $('#resetSkewt').hide();
- $('#deleteSkewt').hide();
+function createHysplit(callsign, adjustment) {
+ var vehicle = vehicles[callsign];
- skewt = new SkewT('#skewt-plot');
-
- try {
- skewt.plot(skewtdata);
- $('#resetSkewt').show();
- $('#deleteSkewt').show();
- }
- catch(err) {}
-}
+ var altitude = Math.round(vehicle.curr_position.gps_alt) + adjustment;
-function skewTdraw (callsign) {
- // Open sidebar
- var box = $("#skewtbox");
+ var endTime = new Date(Date.parse(vehicle.curr_position.gps_time));
+ endTime.setHours(endTime.getHours() + 84);
+ endTime = endTime.toISOString();
- if(box.is(':hidden')) {
- $('.flatpage, #homebox').hide();
- $('.skewt').show();
- box.show().scrollTop(0);
- checkSize();
- };
+ var url = "https://predict.cusf.co.uk/api/v1/?profile=float_profile"
+ + "&launch_latitude=" + vehicle.curr_position.gps_lat
+ + "&launch_longitude=" + vehicle.curr_position.gps_lon
+ + "&launch_altitude=" + (altitude-1)
+ + "&launch_datetime=" + vehicle.curr_position.gps_time
+ + "&ascent_rate=0.1"
+ + "&float_altitude=" + altitude
+ + "&stop_datetime=" + endTime;
- // Delete existing
- try {
- skewt.clear();
- } catch (err) {}
-
- $('#resetSkewt').hide();
- $('#deleteSkewt').hide();
- $("#skewt-plot").empty();
- $("#skewtErrors").text("");
- $("#skewtErrors").hide();
-
- // Loading gif
- $("#skewtLoading").show();
- $("#skewtSerial").show();
- $("#skewtSerial").text("Serial: " + callsign);
-
- // Download Data
- var data_url = "https://api.v2.sondehub.org/sonde/" + encodeURIComponent(callsign);
$.ajax({
type: "GET",
- url: data_url,
+ url: url,
dataType: "json",
success: function(data) {
- processSkewT(data);
- }
- });
-
- // Credit https://github.com/projecthorus/sondehub-card/blob/main/js/utils.js#L116
- function processSkewT (data) {
- burst_idx = -1;
- max_alt = -99999.0;
- for (let i = 0; i < data.length; i++){
- alt = parseFloat(data[i].alt);
- if (alt > max_alt){
- max_alt = alt;
- burst_idx = i;
- }
- }
- if(data.length < 50){
- $("#skewtErrors").text("Insufficient data for Skew-T plot (<50 points).");
- $("#skewtErrors").show();
- return;
- }
-
- // Check that we have ascent data
- if (burst_idx <= 0){
- $("#skewtErrors").text("Insufficient data for Skew-T plot (Only descent data available).");
- $("#skewtErrors").show();
- return;
- }
-
- // Check that the first datapoint is at a reasonable altitude.
- if (data[0].alt > 15000){
- $("#skewtErrors").text("Insufficient data for Skew-T plot (Only data > 15km available)");
- $("#skewtErrors").show();
- return;
- }
-
- var skewt_data = [];
- decimation = 10;
-
- idx = 1;
-
- while (idx < burst_idx){
- entry = data[idx];
- old_entry = data[idx-1];
-
- _old_date = new Date(old_entry.datetime);
- _new_date = new Date(entry.datetime);
- _time_delta = (_new_date - _old_date)/1000.0;
- if (_time_delta <= 0){
- idx = idx + 1;
- continue;
- }
-
- _temp = null;
- _dewp = -1000.0;
- _pressure = null;
-
- // Extract temperature datapoint
- if (entry.hasOwnProperty('temp')){
- if(parseFloat(entry.temp) > -270.0){
- _temp = parseFloat(entry.temp);
- } else{
- idx = idx + 1;
- continue;
- }
- }else{
- // No temp data. Skip to the next point
- idx = idx + 1;
- continue;
- }
-
- // Try and extract RH datapoint
- if (entry.hasOwnProperty('humidity')){
- if(parseFloat(entry.humidity) >= 0.0){
- _rh = parseFloat(entry.humidity);
- // Calculate the dewpoint
- _dewp = (243.04 * (Math.log(_rh / 100) + ((17.625 * _temp) / (243.04 + _temp))) / (17.625 - Math.log(_rh / 100) - ((17.625 * _temp) / (243.04 + _temp))));
- } else {
- _dewp = -1000.0;
- }
- }
-
- // Calculate movement
- _old_pos = {'lat': old_entry.lat, 'lon': old_entry.lon, 'alt': old_entry.alt};
- _new_pos = {'lat': entry.lat, 'lon': entry.lon, 'alt': entry.alt};
-
- _pos_info = calculate_lookangles(_old_pos, _new_pos);
- _wdir = (_pos_info['azimuth']+180.0)%360.0;
- _wspd = _pos_info['great_circle_distance']/_time_delta;
-
- if (entry.hasOwnProperty('pressure')){
- _pressure = entry.pressure;
- } else {
- // Otherwise, calculate it
- _pressure = getPressure(_new_pos.alt);
- }
-
- if(_pressure < 50.0){
- break;
- }
-
- _new_skewt_data = {"press": _pressure, "hght": _new_pos.alt, "temp": _temp, "dwpt": _dewp, "wdir": _wdir, "wspd": _wspd};
-
- skewt_data.push(_new_skewt_data);
-
- idx = idx + decimation;
- }
-
- skewtdata = skewt_data;
-
- $("#skewtLoading").hide();
-
- if (skewtdata.length > 0){
-
- if(box.is(':hidden')) {
- $('.flatpage, #homebox').hide();
- $('.skewt').show();
- box.show().scrollTop(0);
- checkSize();
- };
-
- skewt = new SkewT('#skewt-plot');
-
- try {
- skewt.plot(skewtdata);
- $('#resetSkewt').show();
- $('#deleteSkewt').show();
+ var path = [[vehicle.curr_position.gps_lat, vehicle.curr_position.gps_lon]];
+ for (let point in data.prediction[1].trajectory) {
+ path.push([data.prediction[1].trajectory[point].latitude, data.prediction[1].trajectory[point].longitude]);
}
- catch(err) {}
-
- } else {
- $("#skewtErrors").show();
- $("#skewtErrors").text("Insufficient Data available, or no Temperature/Humidity data available to generate Skew-T plot.");
- };
- }
-};
+ vehicle.prediction_hysplit[adjustment] = new L.Wrapped.Polyline(path);
+ vehicle.prediction_hysplit[adjustment].addTo(map)
+ }
+ });
+}
function set_polyline_visibility(vcallsign, val) {
var vehicle = vehicles[vcallsign];
@@ -2549,6 +2388,7 @@ function addPosition(position) {
prediction_burst: null,
prediction_launch: null,
prediction_launch_polyline: null,
+ prediction_hysplit: {},
ascent_rate: 0.0,
horizontal_rate: 0.0,
max_alt: parseFloat(position.gps_alt),