Files
test/wx.html
2025-11-27 22:25:36 +00:00

194 lines
9.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NWS Weather Data Aggregator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.chart-img {
display: block;
margin: 0 auto;
}
#output {
height: 500px;
}
</style>
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
<div class="container mx-auto p-4 md:p-8">
<header class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white">NWS Weather Data Aggregator</h1>
<p class="text-md text-gray-600 dark:text-gray-400 mt-2">Click the button to fetch and combine weather data for analysis.</p>
</header>
<!-- Data Fetching Section -->
<section class="mb-12 bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg">
<div class="flex flex-col items-center">
<button id="fetchButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-colors duration-300 text-lg">
Fetch All Data
</button>
<div id="loader" class="loader mt-6 hidden"></div>
<p id="status" class="mt-4 text-gray-600 dark:text-gray-400"></p>
</div>
<div id="outputContainer" class="mt-6 hidden">
<h2 class="text-2xl font-semibold mb-4 text-gray-900 dark:text-white">Combined Data Output</h2>
<textarea id="output" class="w-full p-4 border border-gray-300 dark:border-gray-600 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-800 dark:text-gray-200" readonly></textarea>
<button id="copyButton" class="mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-300">
Copy to Clipboard
</button>
</div>
</section>
<!-- Graphical Forecasts Section -->
<section>
<h2 class="text-2xl font-semibold border-b-2 border-gray-500 pb-2 mb-6 text-gray-900 dark:text-white">Graphical Forecasts (for reference)</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-lg">
<h3 class="font-bold text-xl mb-4 text-center">Surface Analysis</h3>
<img class="chart-img w-full rounded-md" src="https://www.wpc.ncep.noaa.gov/sfc/lrgnamsfc.gif" alt="Surface Analysis Chart">
</div>
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-lg">
<h3 class="font-bold text-xl mb-4 text-center">500mb Heights & Vorticity</h3>
<img class="chart-img w-full rounded-md" src="https://www.wpc.ncep.noaa.gov/upperair/nam.gif" alt="500mb Analysis Chart">
</div>
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-lg">
<h3 class="font-bold text-xl mb-4 text-center">850mb Temps, Heights & Winds</h3>
<img class="chart-img w-full rounded-md" src="https://www.wpc.ncep.noaa.gov/upperair/nam85.gif" alt="850mb Analysis Chart">
</div>
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-lg">
<h3 class="font-bold text-xl mb-4 text-center">250mb Jet Stream</h3>
<img class="chart-img w-full rounded-md" src="https://www.wpc.ncep.noaa.gov/upperair/namhi.gif" alt="250mb Analysis Chart">
</div>
</div>
</section>
<footer class="text-center mt-12 py-4 border-t dark:border-gray-700">
<p class="text-sm text-gray-500 dark:text-gray-400">Data provided by the National Weather Service.</p>
</footer>
</div>
<script>
// --- DOM Element References ---
const fetchButton = document.getElementById('fetchButton');
const copyButton = document.getElementById('copyButton');
const outputContainer = document.getElementById('outputContainer');
const outputTextarea = document.getElementById('output');
const loader = document.getElementById('loader');
const statusText = document.getElementById('status');
// --- Data Source URLs ---
const sources = {
areaForecastDiscussion: `https://forecast.weather.gov/product.php?site=RLX&issuedby=RLX&product=AFD&format=txt&version=1&glossary=0`,
spcOutlook: `https://www.spc.noaa.gov/products/outlook/day1otlk.html`,
wpcShortRange: `https://www.wpc.ncep.noaa.gov/discussions/pmdspd.html`,
wpcMediumRange: `https://www.wpc.ncep.noaa.gov/discussions/pmdepd.html`
};
/**
* Fetches content from a URL and extracts text from a <pre> tag.
* @param {string} url - The URL to fetch data from.
* @param {string} key - A key to identify the data source for error messages.
* @returns {Promise<string>} A promise that resolves with the extracted text.
*/
async function fetchAndParse(url, key) {
try {
// NOTE: The CORS proxy has been removed as requested.
// If you experience fetching errors when hosting this on a static site,
// it's likely due to the browser's same-origin policy blocking the request.
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const preElement = doc.querySelector('pre');
return preElement ? preElement.innerText.trim() : `Could not find <pre> content for ${key}.`;
} catch (error) {
console.error(`Error fetching ${key}:`, error);
statusText.innerHTML += `<br><span class="text-red-500">Failed to fetch ${key}.</span>`;
return `Error fetching data for ${key}: ${error.message}`;
}
}
/**
* Main function to orchestrate fetching all data sources.
*/
async function fetchAllData() {
// --- UI Updates: Start Loading ---
fetchButton.disabled = true;
fetchButton.classList.add('opacity-50', 'cursor-not-allowed');
loader.classList.remove('hidden');
statusText.textContent = 'Fetching data...';
outputContainer.classList.add('hidden');
const data = {};
// --- Fetch all sources concurrently ---
const promises = [
fetchAndParse(sources.areaForecastDiscussion, 'Area Forecast Discussion').then(text => data.areaForecastDiscussion = text),
fetchAndParse(sources.spcOutlook, 'SPC Outlook').then(text => data.spcOutlook = text),
fetchAndParse(sources.wpcShortRange, 'WPC Short Range').then(text => data.wpcShortRange = text),
fetchAndParse(sources.wpcMediumRange, 'WPC Medium Range').then(text => data.wpcMediumRange = text)
];
await Promise.all(promises);
// --- UI Updates: Display Results ---
loader.classList.add('hidden');
statusText.textContent = 'Data fetching complete!';
// Format the collected data as a JSON string and display it
outputTextarea.value = JSON.stringify(data, null, 2);
outputContainer.classList.remove('hidden');
// Re-enable the fetch button
fetchButton.disabled = false;
fetchButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
/**
* Copies the content of the textarea to the user's clipboard.
*/
function copyToClipboard() {
outputTextarea.select();
// Using document.execCommand as a fallback for broader compatibility
// in environments where navigator.clipboard might be restricted.
try {
document.execCommand('copy');
copyButton.textContent = 'Copied!';
setTimeout(() => { copyButton.textContent = 'Copy to Clipboard'; }, 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
copyButton.textContent = 'Copy Failed!';
setTimeout(() => { copyButton.textContent = 'Copy to Clipboard'; }, 2000);
}
}
// --- Event Listeners ---
fetchButton.addEventListener('click', fetchAllData);
copyButton.addEventListener('click', copyToClipboard);
</script>
</body>
</html>