Skip to content

Commit e26bc33

Browse files
Added experimental ACE calc feature
1 parent 88420de commit e26bc33

File tree

9 files changed

+161
-2
lines changed

9 files changed

+161
-2
lines changed

index.html

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,92 @@
7373
animation: octocat-wave 560ms ease-in-out
7474
}
7575
}
76+
77+
/* ACE modal styles */
78+
#ace-results.ace-card {
79+
position: fixed;
80+
right: 1rem;
81+
bottom: 1rem;
82+
z-index: 1000;
83+
display: flex;
84+
flex-direction: column;
85+
gap: .35rem;
86+
padding: .75rem .9rem;
87+
width: clamp(220px, 28vw, 340px);
88+
max-height: min(46vh, 420px);
89+
color: #fff;
90+
background: rgba(24, 24, 24, 0.92);
91+
backdrop-filter: blur(6px);
92+
border-radius: .6rem;
93+
box-shadow: 0 10px 30px rgba(0,0,0,.35);
94+
border: 1px solid rgba(255,255,255,.08);
95+
}
96+
97+
#ace-results.ace-card h3 {
98+
margin: 0;
99+
font-size: 1rem;
100+
font-weight: 700;
101+
letter-spacing: .02em;
102+
border-bottom: 1px solid rgba(255,255,255,.08);
103+
padding-bottom: .35rem;
104+
}
105+
106+
#ace-results .ace-total {
107+
font-weight: 600;
108+
color: #fbaf00;
109+
}
110+
111+
#ace-results .ace-list {
112+
list-style: none;
113+
margin: .25rem 0 0 0;
114+
padding: 0;
115+
overflow: auto;
116+
flex: 1 1 auto;
117+
}
118+
119+
#ace-results .ace-list li {
120+
display: flex;
121+
justify-content: space-between;
122+
align-items: baseline;
123+
gap: .5rem;
124+
padding: .25rem 0;
125+
border-bottom: 1px dashed rgba(255,255,255,.08);
126+
font-size: .95rem;
127+
line-height: 1.2;
128+
}
129+
130+
#ace-results .ace-list li:last-child {
131+
border-bottom: none;
132+
}
133+
134+
#ace-results .ace-list li span {
135+
opacity: .75;
136+
font-size: .85em;
137+
white-space: nowrap;
138+
}
139+
140+
@media (max-width: 640px) {
141+
#ace-results.ace-card {
142+
left: .75rem;
143+
right: .75rem;
144+
bottom: .75rem;
145+
width: auto;
146+
max-height: 40vh;
147+
padding: .65rem .75rem;
148+
}
149+
}
150+
151+
@media (prefers-color-scheme: light) {
152+
#ace-results.ace-card {
153+
color: #111;
154+
background: rgba(255,255,255,.95);
155+
border: 1px solid rgba(0,0,0,.08);
156+
}
157+
158+
#ace-results .ace-list li {
159+
border-bottom-color: rgba(0,0,0,.08);
160+
}
161+
}
76162
</style>
77163

78164
<div id="map-indicator">
@@ -302,6 +388,13 @@ <h4>Create/edit custom scales</h4>
302388
</button>
303389
</div>
304390

391+
<!-- ACE results panel -->
392+
<div id="ace-results" class="ace-card hidden" aria-live="polite">
393+
<h3>ACE</h3>
394+
<div class="ace-total">Total: —</div>
395+
<ul class="ace-list"></ul>
396+
</div>
397+
305398
<script src="static/js/sw.js"></script>
306399
<script src="static/js/pages.js"></script>
307400
<script src="static/js/generate.js"></script>

static/css/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ ion-icon+span {
358358
}
359359

360360
.hidden {
361-
display: none;
361+
display: none !important;
362362
}
363363

364364
#image-container {

static/js/atcf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function parseAtcf(data) {
2828
name: cols[1],
2929
shape: getAtcfShape(cols[10]),
3030
category: speedToCat(Number(cols[8])),
31+
speed: Number(cols[8]),
3132
latitude: cols[6].slice(0, -2) + "." + cols[6].slice(-2),
3233
longitude: cols[7].slice(0, -2) + "." + cols[7].slice(-2)
3334
}

static/js/generate.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ class MapManager {
357357
mapManager.hideLoader();
358358
mapManager.state.loading = false;
359359
}
360+
const acePanel = document.getElementById("ace-results");
361+
if (acePanel) acePanel.classList.add("hidden");
360362
}
361363

362364
handleCustomMapUpload(event) {
@@ -552,6 +554,50 @@ function normalizeLongitude(lng) {
552554
return ((lng + 180) % 360 + 360) % 360 - 180;
553555
}
554556

557+
// ACE helpers
558+
function computeACEByStorm(points) {
559+
const groups = points.reduce((acc, p) => {
560+
const k = p.name || "Unnamed";
561+
(acc[k] ??= []).push(p);
562+
return acc;
563+
}, {});
564+
const storms = [];
565+
let total = 0;
566+
567+
Object.entries(groups).forEach(([name, arr]) => {
568+
let sum = 0, pts = 0, tsPts = 0;
569+
arr.forEach(p => {
570+
const v = Number(p.speed);
571+
if (!Number.isFinite(v)) return;
572+
pts++;
573+
if (v >= 34) {
574+
const v5 = Math.round(v / 5) * 5; // NOAA convention
575+
sum += v5 * v5;
576+
tsPts++;
577+
}
578+
});
579+
const ace = +(sum / 10000).toFixed(2);
580+
storms.push({ name, ace, points: pts, tsPoints: tsPts });
581+
total += ace;
582+
});
583+
584+
storms.sort((a, b) => b.ace - a.ace);
585+
return { totalAce: +total.toFixed(2), storms };
586+
}
587+
588+
function renderACEResults(ace) {
589+
const container = document.getElementById("ace-results");
590+
if (!container) return;
591+
container.classList.remove("hidden");
592+
container.innerHTML = `
593+
<h3 style="margin:.25rem 0;">ACE</h3>
594+
<div class="ace-total">Total: ${ace.totalAce}</div>
595+
<ul class="ace-list">
596+
${ace.storms.map(s => `<li>${s.name || "Unnamed"}: ${s.ace} <span>(pts: ${s.tsPoints}/${s.points})</span></li>`).join("")}
597+
</ul>
598+
`;
599+
}
600+
555601
// determine point type (tropical/subtropical/extratropical) from available fields
556602
function getPointType(point) {
557603
const normalizeType = (t) => {
@@ -575,6 +621,10 @@ function createMap(data, accessible) {
575621
const imageContainer = elements.imageContainer;
576622
const smallerDotsCheckbox = document.getElementById("smaller-dots");
577623

624+
// Hide ACE panel while generating to avoid stale values
625+
const acePanel = document.getElementById("ace-results");
626+
if (acePanel) acePanel.classList.add("hidden");
627+
578628
closeButton.classList.remove("hidden");
579629
output.classList.add("hidden");
580630
loader.classList.remove("hidden");
@@ -803,6 +853,13 @@ function createMap(data, accessible) {
803853
loader.classList.add("hidden");
804854
output.classList.remove("hidden");
805855

856+
try {
857+
const ace = computeACEByStorm(data);
858+
renderACEResults(ace);
859+
} catch (e) {
860+
console.warn("ACE calculation failed:", e);
861+
}
862+
806863
// if map generation is successful, hide the loader icon
807864
mapManager.hideLoader();
808865
mapManager.updateStatus('success');
@@ -815,5 +872,8 @@ function createMap(data, accessible) {
815872
mapManager.hideLoader();
816873
mapManager.updateStatus('error');
817874
mapManager.state.loading = false;
875+
876+
const acePanel = document.getElementById("ace-results");
877+
if (acePanel) acePanel.classList.add("hidden");
818878
});
819879
}

static/js/hurdat.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function parseHurdat(data) {
3232
name: uniqueId,
3333
shape: getHurdatShape(cols[3]),
3434
category: speedToCat(Number(cols[6])),
35+
speed: Number(cols[6]),
3536
latitude: cols[4],
3637
longitude: cols[5]
3738
}

static/js/ibtracs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ function parseIbtracs(data) {
5454
name: cols[0],
5555
shape: getIbtracsShape(cols[10]),
5656
category: speedToCat(max_wind),
57+
speed: max_wind,
5758
latitude: parseCoord(cols[6].trim()),
5859
longitude: parseCoord(cols[7].trim())
5960
}

static/js/manual_input.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ document.querySelector("form").addEventListener("submit", (e) => {
8585
shape: stageToShape(stage),
8686
category: speedToCat(speedInKnots),
8787
latitude: latInput + latDir,
88-
longitude: longInput + longDir
88+
longitude: longInput + longDir,
89+
speed: speedInKnots
8990
});
9091
});
9192

static/js/rsmc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function parseRsmc(data) {
2929
name: uniqueId,
3030
shape: getRsmcShape(cols[2]),
3131
category: speedToCat(Number(cols[6])),
32+
speed: Number(cols[6]),
3233
latitude: cols[3].slice(0, -1) + "." + cols[3].slice(-1) + "N",
3334
longitude: cols[4].slice(0, -1) + "." + cols[4].slice(-1) + "E"
3435
}

static/js/storms.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ function parseStorms(data) {
5454
name: tcNumber,
5555
shape: getStormsShape(category),
5656
category: speedToCat(windSpeedKnots),
57+
speed: windSpeedKnots,
5758
latitude: formatLatitude(parseFloat(cols[5])),
5859
longitude: formatLongitude(parseFloat(cols[6])),
5960
});

0 commit comments

Comments
 (0)