Working with bugs - wind toggle
This commit is contained in:
parent
3f8d192ba7
commit
b5f5bc5fc2
@ -8,35 +8,31 @@ try {
|
|||||||
|
|
||||||
if (isset($_GET['id'])) {
|
if (isset($_GET['id'])) {
|
||||||
// Fetch track for a specific hurricane
|
// Fetch track for a specific hurricane
|
||||||
$hurricaneId = (int)$_GET['id'];
|
$hurricaneId = $_GET['id'];
|
||||||
$stmt = $pdo->prepare('SELECT name, year FROM hurricanes WHERE id = ?');
|
$stmt = $pdo->prepare('SELECT name, season, lat, lon, wind_speed FROM hurricanes WHERE storm_id = ? ORDER BY iso_time ASC');
|
||||||
$stmt->execute([$hurricaneId]);
|
$stmt->execute([$hurricaneId]);
|
||||||
$hurricane = $stmt->fetch();
|
$trackData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$hurricane) {
|
if (!$trackData) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Hurricane not found']);
|
echo json_encode(['error' => 'Hurricane not found']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare('SELECT lat, lon, wind_speed_mph FROM hurricane_tracks WHERE hurricane_id = ? ORDER BY timestamp ASC');
|
|
||||||
$stmt->execute([$hurricaneId]);
|
|
||||||
$trackData = $stmt->fetchAll();
|
|
||||||
|
|
||||||
// Format for existing frontend: [lon, lat, wind]
|
// Format for existing frontend: [lon, lat, wind]
|
||||||
$formattedTrack = array_map(function($point) {
|
$formattedTrack = array_map(function($point) {
|
||||||
return [(float)$point['lon'], (float)$point['lat'], (int)$point['wind_speed_mph']];
|
return [(float)$point['lon'], (float)$point['lat'], (int)$point['wind_speed']];
|
||||||
}, $trackData);
|
}, $trackData);
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'name' => $hurricane['name'] . ' (' . $hurricane['year'] . ')',
|
'name' => $trackData[0]['name'] . ' (' . $trackData[0]['season'] . ')',
|
||||||
'track' => $formattedTrack
|
'track' => $formattedTrack
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fetch list of all hurricanes
|
// Fetch list of all hurricanes
|
||||||
$stmt = $pdo->query('SELECT id, name, year FROM hurricanes ORDER BY year DESC, name ASC');
|
$stmt = $pdo->query('SELECT storm_id as id, name, season as year FROM hurricanes GROUP BY storm_id, name, season ORDER BY season DESC, name ASC');
|
||||||
$hurricanes = $stmt->fetchAll();
|
$hurricanes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
echo json_encode($hurricanes);
|
echo json_encode($hurricanes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
api/wind.json
Normal file
1
api/wind.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"header":{"nx":36,"ny":18,"lo1":-180,"la1":90,"dx":10,"dy":10,"parameterCategory":2,"parameterNumber":2,"forecastTime":0,"refTime":"2025-10-14T12:15:46.0000"},"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"header":{"nx":36,"ny":18,"lo1":-180,"la1":90,"dx":10,"dy":10,"parameterCategory":2,"parameterNumber":3,"forecastTime":0,"refTime":"2025-10-14T12:15:46.0000"},"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}]
|
||||||
124
api/wind.php
124
api/wind.php
@ -4,80 +4,75 @@ ini_set('display_errors', 1);
|
|||||||
ini_set('display_startup_errors', 1);
|
ini_set('display_startup_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
require_once __DIR__ . '/../config.php';
|
$cacheFile = __DIR__ . '/wind.json';
|
||||||
|
$cacheTime = 3600; // 1 hour
|
||||||
|
|
||||||
$bulkFileName = 'weather_14.json.gz';
|
// Check if a cached file exists and is recent
|
||||||
$bulkUrl = "https://bulk.openweathermap.org/snapshot/{$bulkFileName}?appid=" . OWM_API_KEY;
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
// Use a stream context to capture HTTP status headers
|
readfile($cacheFile);
|
||||||
$context = stream_context_create(['http' => ['ignore_errors' => true]]);
|
|
||||||
$gzData = @file_get_contents($bulkUrl, false, $context);
|
|
||||||
|
|
||||||
// Check for errors and specific HTTP status codes
|
|
||||||
if ($gzData === false || !isset($http_response_header[0]) || strpos($http_response_header[0], '200 OK') === false) {
|
|
||||||
$error_message = 'Failed to download bulk weather data.';
|
|
||||||
if (isset($http_response_header[0])) {
|
|
||||||
if (strpos($http_response_header[0], '401 Unauthorized') !== false) {
|
|
||||||
$error_message = 'OpenWeatherMap API key is invalid for the Bulk Data service. The key may be correct for other services like weather tiles, but lacks permission for bulk downloads.';
|
|
||||||
http_response_code(401);
|
|
||||||
} else {
|
|
||||||
$error_message .= ' Server responded with: ' . $http_response_header[0];
|
|
||||||
http_response_code(500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
http_response_code(500);
|
|
||||||
}
|
|
||||||
echo json_encode(['error' => $error_message]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$jsonData = gzdecode($gzData);
|
// --- Grid setup ---
|
||||||
if ($jsonData === false) {
|
$nx = 36; // Grid points in longitude (every 10 degrees)
|
||||||
http_response_code(500);
|
$ny = 18; // Grid points in latitude (every 10 degrees)
|
||||||
echo json_encode(['error' => 'Failed to decompress weather data.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The file contains JSON objects separated by newlines
|
|
||||||
$lines = explode("\n", trim($jsonData));
|
|
||||||
$cities = [];
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
if (!empty($line)) {
|
|
||||||
$cities[] = json_decode($line, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($cities)) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => 'Failed to parse weather data JSON or file is empty.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Interpolation ---
|
|
||||||
|
|
||||||
$nx = 72; // Grid points in longitude (every 5 degrees)
|
|
||||||
$ny = 37; // Grid points in latitude (every 5 degrees)
|
|
||||||
$lo1 = -180;
|
$lo1 = -180;
|
||||||
$la1 = 90;
|
$la1 = 90;
|
||||||
$dx = 5;
|
$dx = 10;
|
||||||
$dy = 5;
|
$dy = 10;
|
||||||
|
|
||||||
$uData = [];
|
$uData = array_fill(0, $nx * $ny, 0);
|
||||||
$vData = [];
|
$vData = array_fill(0, $nx * $ny, 0);
|
||||||
|
|
||||||
|
// --- Build coordinate arrays for batch API call ---
|
||||||
|
$lats = [];
|
||||||
|
$lons = [];
|
||||||
for ($j = 0; $j < $ny; $j++) {
|
for ($j = 0; $j < $ny; $j++) {
|
||||||
$lat = $la1 - $j * $dy;
|
$lat = $la1 - $j * $dy;
|
||||||
for ($i = 0; $i < $nx; $i++) {
|
for ($i = 0; $i < $nx; $i++) {
|
||||||
$lon = $lo1 + $i * $dx;
|
$lon = $lo1 + $i * $dx;
|
||||||
$wind = interpolatePoint($lat, $lon, $cities);
|
$lats[] = $lat;
|
||||||
$uData[] = $wind['u'];
|
$lons[] = $lon;
|
||||||
$vData[] = $wind['v'];
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Fetch data from Open-Meteo in a single call ---
|
||||||
|
$url = "https://api.open-meteo.com/v1/forecast?latitude=" . implode(',', $lats) . "&longitude=" . implode(',', $lons) . "&hourly=windspeed_10m,winddirection_10m¤t_weather=true";
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response) {
|
||||||
|
$results = json_decode($response, true);
|
||||||
|
|
||||||
|
// The API returns an array of results, one for each coordinate pair
|
||||||
|
if (is_array($results)) {
|
||||||
|
foreach ($results as $index => $data) {
|
||||||
|
if (isset($data['current_weather'])) {
|
||||||
|
$windspeed = $data['current_weather']['windspeed'];
|
||||||
|
$winddirection = $data['current_weather']['winddirection'];
|
||||||
|
|
||||||
|
// Convert to u and v components
|
||||||
|
$angle = ($winddirection + 180) * M_PI / 180;
|
||||||
|
$u = $windspeed * cos($angle);
|
||||||
|
$v = $windspeed * sin($angle);
|
||||||
|
|
||||||
|
// The index in the response corresponds to the index in our grid
|
||||||
|
$uData[$index] = $u;
|
||||||
|
$vData[$index] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Format Data ---
|
// --- Format Data ---
|
||||||
|
$refTime = gmdate("Y-m-d\\TH:i:s.vZ");
|
||||||
$refTime = gmdate("Y-m-d\\TH:i:s.v\\Z");
|
|
||||||
|
|
||||||
$formattedData = [
|
$formattedData = [
|
||||||
[
|
[
|
||||||
@ -112,13 +107,12 @@ $formattedData = [
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Caching and Output ---
|
$json_data = json_encode($formattedData);
|
||||||
$cacheDir = dirname($cacheFile);
|
|
||||||
if (!is_dir($cacheDir)) {
|
|
||||||
mkdir($cacheDir, 0755, true);
|
|
||||||
}
|
|
||||||
file_put_contents($cacheFile, json_encode($formattedData));
|
|
||||||
|
|
||||||
echo json_encode($formattedData);
|
// Cache the result
|
||||||
|
file_put_contents($cacheFile, $json_data);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo $json_data;
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@ -140,6 +140,8 @@ function initializeGlobe() {
|
|||||||
|
|
||||||
spcDataSource.entities.removeAll();
|
spcDataSource.entities.removeAll();
|
||||||
|
|
||||||
|
const selectedSpcLevels = Array.from(document.querySelectorAll('.spc-checkbox:checked')).map(cb => cb.value);
|
||||||
|
|
||||||
const spcColors = {
|
const spcColors = {
|
||||||
'TSTM': Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.5), // General Thunderstorms
|
'TSTM': Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.5), // General Thunderstorms
|
||||||
'MRGL': Cesium.Color.fromCssColorString('#00C800').withAlpha(0.5), // Marginal
|
'MRGL': Cesium.Color.fromCssColorString('#00C800').withAlpha(0.5), // Marginal
|
||||||
@ -149,18 +151,22 @@ function initializeGlobe() {
|
|||||||
'HIGH': Cesium.Color.fromCssColorString('#FF00FF').withAlpha(0.5) // High
|
'HIGH': Cesium.Color.fromCssColorString('#FF00FF').withAlpha(0.5) // High
|
||||||
};
|
};
|
||||||
|
|
||||||
spcData.forEach(feature => {
|
if (Array.isArray(spcData)) {
|
||||||
const color = spcColors[feature.name] || Cesium.Color.GRAY.withAlpha(0.5);
|
spcData.forEach(feature => {
|
||||||
spcDataSource.entities.add({
|
if (feature && feature.name && Array.isArray(feature.coordinates) && selectedSpcLevels.includes(feature.name)) {
|
||||||
name: `SPC Outlook: ${feature.name}`,
|
const color = spcColors[feature.name] || Cesium.Color.GRAY.withAlpha(0.5);
|
||||||
polygon: {
|
spcDataSource.entities.add({
|
||||||
hierarchy: Cesium.Cartesian3.fromDegreesArray(feature.coordinates),
|
name: `SPC Outlook: ${feature.name}`,
|
||||||
material: color,
|
polygon: {
|
||||||
outline: true,
|
hierarchy: Cesium.Cartesian3.fromDegreesArray(feature.coordinates),
|
||||||
outlineColor: Cesium.Color.BLACK
|
material: color,
|
||||||
|
outline: true,
|
||||||
|
outlineColor: Cesium.Color.BLACK
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
console.log('SPC data source updated.');
|
console.log('SPC data source updated.');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -180,23 +186,39 @@ function initializeGlobe() {
|
|||||||
|
|
||||||
weatherAlertsDataSource.entities.removeAll();
|
weatherAlertsDataSource.entities.removeAll();
|
||||||
|
|
||||||
if (alertsData.alerts) {
|
const selectedAlerts = Array.from(document.querySelectorAll('.alert-checkbox:checked')).map(cb => cb.value);
|
||||||
alertsData.alerts.forEach(alert => {
|
|
||||||
const alertColor = Cesium.Color.ORANGE.withAlpha(0.5);
|
|
||||||
|
|
||||||
// The API provides polygons, so we need to handle them
|
const alertColors = {
|
||||||
if (alert.geometry && alert.geometry.type === 'Polygon') {
|
'tornado': Cesium.Color.RED.withAlpha(0.7),
|
||||||
const coordinates = alert.geometry.coordinates[0].flat();
|
'thunderstorm': Cesium.Color.YELLOW.withAlpha(0.7),
|
||||||
weatherAlertsDataSource.entities.add({
|
'flood': Cesium.Color.BLUE.withAlpha(0.7),
|
||||||
name: alert.properties.event || 'Weather Alert',
|
'wind': Cesium.Color.CYAN.withAlpha(0.7),
|
||||||
description: alert.properties.description || 'No description available.',
|
'snow_ice': Cesium.Color.WHITE.withAlpha(0.7),
|
||||||
polygon: {
|
'fog': Cesium.Color.GRAY.withAlpha(0.7),
|
||||||
hierarchy: Cesium.Cartesian3.fromDegreesArray(coordinates),
|
'extreme_high_temperature': Cesium.Color.ORANGE.withAlpha(0.7)
|
||||||
material: alertColor,
|
};
|
||||||
outline: true,
|
|
||||||
outlineColor: Cesium.Color.BLACK
|
if (Array.isArray(alertsData)) {
|
||||||
|
alertsData.forEach(alert => {
|
||||||
|
if (alert && alert.properties && typeof alert.properties.event === 'string') {
|
||||||
|
const eventType = alert.properties.event.toLowerCase().replace(/\s/g, '_');
|
||||||
|
if (selectedAlerts.includes(eventType)) {
|
||||||
|
const alertColor = alertColors[eventType] || Cesium.Color.PURPLE.withAlpha(0.7);
|
||||||
|
|
||||||
|
if (alert.geometry && alert.geometry.type === 'Polygon' && Array.isArray(alert.geometry.coordinates) && Array.isArray(alert.geometry.coordinates[0])) {
|
||||||
|
const coordinates = alert.geometry.coordinates[0].flat();
|
||||||
|
weatherAlertsDataSource.entities.add({
|
||||||
|
name: alert.properties.event || 'Weather Alert',
|
||||||
|
description: alert.properties.description || 'No description available.',
|
||||||
|
polygon: {
|
||||||
|
hierarchy: Cesium.Cartesian3.fromDegreesArray(coordinates),
|
||||||
|
material: alertColor,
|
||||||
|
outline: true,
|
||||||
|
outlineColor: Cesium.Color.BLACK
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -207,6 +229,14 @@ function initializeGlobe() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.spc-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', loadSpcData);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.alert-checkbox').forEach(cb => {
|
||||||
|
cb.addEventListener('change', loadWeatherAlerts);
|
||||||
|
});
|
||||||
|
|
||||||
// Load all data sources
|
// Load all data sources
|
||||||
loadWildfireData();
|
loadWildfireData();
|
||||||
loadSpcData();
|
loadSpcData();
|
||||||
@ -752,6 +782,26 @@ function initializeGlobe() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const displayModeSelect = document.getElementById('displayModeSelect');
|
||||||
|
const windControls = document.getElementById('wind-controls');
|
||||||
|
const catControls = document.getElementById('cat-controls');
|
||||||
|
|
||||||
|
displayModeSelect.addEventListener('change', function(e) {
|
||||||
|
if (this.value === 'wind') {
|
||||||
|
windControls.style.display = 'block';
|
||||||
|
catControls.style.display = 'none';
|
||||||
|
windLayer.setVisible(true);
|
||||||
|
catSimulationDataSource.entities.removeAll();
|
||||||
|
} else {
|
||||||
|
windControls.style.display = 'none';
|
||||||
|
catControls.style.display = 'block';
|
||||||
|
windLayer.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger the change event to set the initial state
|
||||||
|
displayModeSelect.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('A critical error occurred while initializing the Cesium viewer:', error);
|
console.error('A critical error occurred while initializing the Cesium viewer:', error);
|
||||||
const cesiumContainer = document.getElementById('cesiumContainer');
|
const cesiumContainer = document.getElementById('cesiumContainer');
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Original code from https://github.com/RaymanNg/3D-Wind-Field
|
* Original code from https://github.com/RaymanNg/3D-Wind-Field
|
||||||
* under the MIT license.
|
* under the MIT license.
|
||||||
@ -22,7 +21,7 @@
|
|||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -34,35 +33,41 @@ class WindLayer {
|
|||||||
this.ellipsoid = viewer.scene.globe.ellipsoid;
|
this.ellipsoid = viewer.scene.globe.ellipsoid;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.windData = null;
|
this.windData = null;
|
||||||
this.primitive = null;
|
this.particleSystem = null;
|
||||||
this.visible = true;
|
this.visible = false;
|
||||||
|
|
||||||
this.init();
|
// A promise that resolves when the layer is ready
|
||||||
|
this.readyPromise = this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
init() {
|
||||||
await this.loadWindData();
|
return this.loadWindData().then(() => {
|
||||||
if (this.windData) {
|
if (this.windData) {
|
||||||
this.particleSystem = new ParticleSystem(this.scene, {
|
this.particleSystem = new ParticleSystem(this.scene, {
|
||||||
windData: this.windData,
|
windData: this.windData,
|
||||||
...this.options.particleSystem
|
...this.options.particleSystem
|
||||||
});
|
});
|
||||||
this.primitive = this.particleSystem.primitive;
|
// Apply the stored visibility state once ready
|
||||||
this.scene.primitives.add(this.primitive);
|
this.particleSystem.polylines.show = this.visible;
|
||||||
}
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Error initializing WindLayer:", error);
|
||||||
|
// Propagate error to allow for further handling
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadWindData() {
|
async loadWindData() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.options.windDataUrl || 'api/wind.php');
|
const response = await fetch(this.options.windDataUrl || 'api/wind.php');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.windData = this.processWindData(data);
|
this.windData = this.processWindData(data);
|
||||||
console.log('Wind data loaded and processed.');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading or processing wind data:', error);
|
console.error('Error loading or processing wind data:', error);
|
||||||
|
throw error; // Re-throw to be caught by the init promise chain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +85,7 @@ class WindLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const header = uComponent.header;
|
const header = uComponent.header;
|
||||||
const windData = {
|
return {
|
||||||
nx: header.nx,
|
nx: header.nx,
|
||||||
ny: header.ny,
|
ny: header.ny,
|
||||||
lo1: header.lo1,
|
lo1: header.lo1,
|
||||||
@ -90,38 +95,48 @@ class WindLayer {
|
|||||||
u: uComponent.data,
|
u: uComponent.data,
|
||||||
v: vComponent.data
|
v: vComponent.data
|
||||||
};
|
};
|
||||||
return windData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(visible) {
|
setVisible(visible) {
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
if (this.primitive) {
|
// Use the promise to safely apply visibility
|
||||||
this.primitive.show = visible;
|
this.readyPromise.then(() => {
|
||||||
}
|
if (this.particleSystem) {
|
||||||
|
this.particleSystem.polylines.show = this.visible;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions(options) {
|
setOptions(options) {
|
||||||
if (this.particleSystem) {
|
this.readyPromise.then(() => {
|
||||||
this.particleSystem.applyOptions(options);
|
if (this.particleSystem) {
|
||||||
}
|
this.particleSystem.applyOptions(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
if (this.particleSystem) {
|
this.readyPromise.then(() => {
|
||||||
this.scene.preRender.removeEventListener(this.particleSystem.update, this.particleSystem);
|
if (this.particleSystem) {
|
||||||
}
|
this.particleSystem.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
play() {
|
||||||
if (this.particleSystem) {
|
this.readyPromise.then(() => {
|
||||||
this.scene.preRender.addEventListener(this.particleSystem.update, this.particleSystem);
|
if (this.particleSystem) {
|
||||||
}
|
this.particleSystem.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setParticleDensity(density) {
|
setParticleDensity(density) {
|
||||||
if (this.particleSystem) {
|
this.readyPromise.then(() => {
|
||||||
this.particleSystem.setParticleCount(density);
|
if (this.particleSystem) {
|
||||||
}
|
this.particleSystem.setParticleCount(density);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,20 +145,34 @@ class ParticleSystem {
|
|||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.windData = options.windData;
|
this.windData = options.windData;
|
||||||
|
|
||||||
|
// Use a polyline collection instead of a point collection
|
||||||
|
this.polylines = this.scene.primitives.add(new Cesium.PolylineCollection());
|
||||||
this.particles = [];
|
this.particles = [];
|
||||||
this.primitive = null;
|
|
||||||
|
|
||||||
this.createParticles();
|
this.createParticles();
|
||||||
this.createPrimitive();
|
|
||||||
|
|
||||||
this.scene.preRender.addEventListener(this.update, this);
|
this.scene.preRender.addEventListener(this.update, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
createParticles() {
|
createParticles() {
|
||||||
const options = this.options;
|
const particleCount = this.options.particleCount || 10000;
|
||||||
const particleCount = options.particleCount || 10000;
|
|
||||||
for (let i = 0; i < particleCount; i++) {
|
for (let i = 0; i < particleCount; i++) {
|
||||||
this.particles.push(this.createParticle());
|
const p = this.createParticle();
|
||||||
|
this.particles.push(p);
|
||||||
|
// Create a polyline for each particle. It will be updated in the update loop.
|
||||||
|
this.polylines.add({
|
||||||
|
positions: [p.position, p.position], // Start with a zero-length line
|
||||||
|
width: this.options.lineWidth || 1.0,
|
||||||
|
material: new Cesium.Material({
|
||||||
|
fabric: {
|
||||||
|
type: 'Color',
|
||||||
|
uniforms: {
|
||||||
|
color: Cesium.Color.WHITE.withAlpha(0.0) // Initially transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +184,7 @@ class ParticleSystem {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
position: position,
|
position: position,
|
||||||
|
previousPosition: position, // Store previous position for the tail of the line
|
||||||
age: Math.floor(Math.random() * (this.options.maxAge || 120)),
|
age: Math.floor(Math.random() * (this.options.maxAge || 120)),
|
||||||
maxAge: this.options.maxAge || 120,
|
maxAge: this.options.maxAge || 120,
|
||||||
speed: Math.random() * (this.options.particleSpeed || 5)
|
speed: Math.random() * (this.options.particleSpeed || 5)
|
||||||
@ -179,11 +209,20 @@ class ParticleSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
if (!this.primitive.show) return;
|
if (this.polylines.length === 0 || !this.polylines.show) return;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.particles.length; i++) {
|
||||||
|
const particle = this.particles[i];
|
||||||
|
const polyline = this.polylines.get(i);
|
||||||
|
|
||||||
this.particles.forEach(particle => {
|
|
||||||
if (particle.age >= particle.maxAge) {
|
if (particle.age >= particle.maxAge) {
|
||||||
Object.assign(particle, this.createParticle());
|
Object.assign(particle, this.createParticle());
|
||||||
|
// Reset polyline to a zero-length, transparent line
|
||||||
|
polyline.positions = [particle.position, particle.position];
|
||||||
|
if (polyline.material && polyline.material.uniforms) {
|
||||||
|
polyline.material.uniforms.color = Cesium.Color.WHITE.withAlpha(0.0);
|
||||||
|
}
|
||||||
|
continue; // Skip to next particle
|
||||||
}
|
}
|
||||||
|
|
||||||
const wind = this.getWind(particle.position);
|
const wind = this.getWind(particle.position);
|
||||||
@ -193,6 +232,9 @@ class ParticleSystem {
|
|||||||
const vx = wind.u * speed / metersPerDegree;
|
const vx = wind.u * speed / metersPerDegree;
|
||||||
const vy = wind.v * speed / metersPerDegree;
|
const vy = wind.v * speed / metersPerDegree;
|
||||||
|
|
||||||
|
// Store current position as the previous one
|
||||||
|
particle.previousPosition = particle.position;
|
||||||
|
|
||||||
const cartographic = Cesium.Cartographic.fromCartesian(particle.position);
|
const cartographic = Cesium.Cartographic.fromCartesian(particle.position);
|
||||||
cartographic.longitude += Cesium.Math.toRadians(vx);
|
cartographic.longitude += Cesium.Math.toRadians(vx);
|
||||||
cartographic.latitude += Cesium.Math.toRadians(vy);
|
cartographic.latitude += Cesium.Math.toRadians(vy);
|
||||||
@ -203,41 +245,25 @@ class ParticleSystem {
|
|||||||
cartographic.height
|
cartographic.height
|
||||||
);
|
);
|
||||||
particle.age++;
|
particle.age++;
|
||||||
});
|
|
||||||
|
|
||||||
this.updatePrimitive();
|
// Update polyline positions to create a line segment
|
||||||
}
|
polyline.positions = [particle.previousPosition, particle.position];
|
||||||
|
// Fade the line based on age
|
||||||
updatePrimitive() {
|
if (polyline.material && polyline.material.uniforms) {
|
||||||
const instances = this.particles.map(particle => {
|
polyline.material.uniforms.color = Cesium.Color.WHITE.withAlpha(1 - (particle.age / particle.maxAge));
|
||||||
return new Cesium.GeometryInstance({
|
}
|
||||||
geometry: new Cesium.SimplePolylineGeometry({
|
|
||||||
positions: [particle.position, particle.position] // Simplified for a dot
|
|
||||||
}),
|
|
||||||
attributes: {
|
|
||||||
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
|
|
||||||
Cesium.Color.WHITE.withAlpha(particle.age / particle.maxAge)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.primitive) {
|
|
||||||
this.scene.primitives.remove(this.primitive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.primitive = new Cesium.Primitive({
|
|
||||||
geometryInstances: instances,
|
|
||||||
appearance: new Cesium.PolylineColorAppearance(),
|
|
||||||
asynchronous: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scene.primitives.add(this.primitive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyOptions(options) {
|
applyOptions(options) {
|
||||||
this.options = Object.assign(this.options, options);
|
this.options = Object.assign(this.options, options);
|
||||||
// Re-create particles or update properties as needed
|
// Apply new options to existing polylines
|
||||||
|
for (let i = 0; i < this.polylines.length; i++) {
|
||||||
|
const polyline = this.polylines.get(i);
|
||||||
|
if (options.lineWidth) {
|
||||||
|
polyline.width = options.lineWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setParticleCount(density) {
|
setParticleCount(density) {
|
||||||
@ -247,10 +273,32 @@ class ParticleSystem {
|
|||||||
}
|
}
|
||||||
const newParticleCount = Math.floor(maxParticles * density);
|
const newParticleCount = Math.floor(maxParticles * density);
|
||||||
|
|
||||||
this.particles.length = 0; // Clear the array
|
this.polylines.removeAll();
|
||||||
|
this.particles = [];
|
||||||
|
|
||||||
for (let i = 0; i < newParticleCount; i++) {
|
for (let i = 0; i < newParticleCount; i++) {
|
||||||
this.particles.push(this.createParticle());
|
const p = this.createParticle();
|
||||||
|
this.particles.push(p);
|
||||||
|
this.polylines.add({
|
||||||
|
positions: [p.position, p.position],
|
||||||
|
width: this.options.lineWidth || 1.0,
|
||||||
|
material: new Cesium.Material({
|
||||||
|
fabric: {
|
||||||
|
type: 'Color',
|
||||||
|
uniforms: {
|
||||||
|
color: Cesium.Color.WHITE.withAlpha(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
this.scene.preRender.removeEventListener(this.update, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
play() {
|
||||||
|
this.scene.preRender.addEventListener(this.update, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
index.php
132
index.php
@ -31,64 +31,96 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<h3>Wind Altitude</h3>
|
<h3>Display Mode</h3>
|
||||||
<input type="range" id="windAltitudeSlider" min="0" max="15000" step="500" value="10000">
|
<select id="displayModeSelect">
|
||||||
<span id="windAltitudeLabel">10000 m</span>
|
<option value="wind">Global Wind</option>
|
||||||
</div>
|
<option value="cat">CAT Simulation</option>
|
||||||
<div class="control-group">
|
|
||||||
<h3>Wind Animation</h3>
|
|
||||||
<button id="playPauseWind">Pause</button>
|
|
||||||
<label>
|
|
||||||
Particle Density:
|
|
||||||
<input type="range" id="particleDensitySlider" min="0.1" max="1.0" step="0.1" value="0.5">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>CAT Simulation</h3>
|
|
||||||
<label for="hurricane-select">Select Hurricane:</label>
|
|
||||||
<select id="hurricane-select" name="hurricane-select">
|
|
||||||
<option value="">Loading storms...</option>
|
|
||||||
</select>
|
</select>
|
||||||
<label for="portfolio-select">Sample Portfolio:</label>
|
</div>
|
||||||
<select id="portfolio-select" name="portfolio-select">
|
<div id="wind-controls">
|
||||||
<option value="">Select a portfolio</option>
|
<div class="control-group">
|
||||||
<option value="assets/portfolios/fl_coastal.csv">Florida Coastal</option>
|
<h3>Wind Altitude</h3>
|
||||||
<option value="assets/portfolios/fl_inland.csv">Florida Inland</option>
|
<input type="range" id="windAltitudeSlider" min="0" max="15000" step="500" value="10000">
|
||||||
<option value="assets/portfolios/la_coastal.csv">Louisiana Coastal</option>
|
<span id="windAltitudeLabel">10000 m</span>
|
||||||
</select>
|
</div>
|
||||||
<label for="portfolio-upload">Custom Portfolio (CSV):</label>
|
<div class="control-group">
|
||||||
<small>CSV must contain 'lat', 'lon', 'tiv', and optionally 'deductible' and 'limit' headers.</small>
|
<h3>Wind Animation</h3>
|
||||||
<input type="file" id="portfolio-upload" accept=".csv" />
|
<button id="playPauseWind">Pause</button>
|
||||||
<div id="portfolio-status"></div>
|
<label>
|
||||||
<button id="runCatSimulation">Run Simulation</button>
|
Particle Density:
|
||||||
<div id="cat-simulation-results" style="display: none;">
|
<input type="range" id="particleDensitySlider" min="0.1" max="1.0" step="0.1" value="0.5">
|
||||||
<h4>Simulation Results</h4>
|
</label>
|
||||||
<p>Total Loss: <span id="total-loss-value">$0</span></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div id="cat-controls" style="display: none;">
|
||||||
<h3>Probabilistic Analysis</h3>
|
<div class="control-group">
|
||||||
<button id="runProbabilisticAnalysis">Run Analysis</button>
|
<h3>CAT Simulation</h3>
|
||||||
<div id="probabilistic-results" style="display: none;">
|
<label for="hurricane-select">Select Hurricane:</label>
|
||||||
<h4>Analysis Results</h4>
|
<select id="hurricane-select" name="hurricane-select">
|
||||||
<p>Average Annual Loss (AAL): <span id="aal-value">$0</span></p>
|
<option value="">Loading storms...</option>
|
||||||
<canvas id="epCurveChart"></canvas>
|
</select>
|
||||||
|
<label for="portfolio-select">Sample Portfolio:</label>
|
||||||
|
<select id="portfolio-select" name="portfolio-select">
|
||||||
|
<option value="">Select a portfolio</option>
|
||||||
|
<option value="assets/portfolios/fl_coastal.csv">Florida Coastal</option>
|
||||||
|
<option value="assets/portfolios/fl_inland.csv">Florida Inland</option>
|
||||||
|
<option value="assets/portfolios/la_coastal.csv">Louisiana Coastal</option>
|
||||||
|
</select>
|
||||||
|
<label for="portfolio-upload">Custom Portfolio (CSV):</label>
|
||||||
|
<small>CSV must contain 'lat', 'lon', 'tiv', and optionally 'deductible' and 'limit' headers.</small>
|
||||||
|
<input type="file" id="portfolio-upload" accept=".csv" />
|
||||||
|
<div id="portfolio-status"></div>
|
||||||
|
<button id="runCatSimulation">Run Simulation</button>
|
||||||
|
<div id="cat-simulation-results" style="display: none;">
|
||||||
|
<h4>Simulation Results</h4>
|
||||||
|
<p>Total Loss: <span id="total-loss-value">$0</span></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<h3>Probabilistic Analysis</h3>
|
||||||
|
<button id="runProbabilisticAnalysis">Run Analysis</button>
|
||||||
|
<div id="probabilistic-results" style="display: none;">
|
||||||
|
<h4>Analysis Results</h4>
|
||||||
|
<p>Average Annual Loss (AAL): <span id="aal-value">$0</span></p>
|
||||||
|
<canvas id="epCurveChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h4>Portfolio Comparison</h4>
|
<h4>Portfolio Comparison</h4>
|
||||||
<label for="portfolio-upload-2">Portfolio 2 (CSV):</label>
|
<label for="portfolio-upload-2">Portfolio 2 (CSV):</label>
|
||||||
<input type="file" id="portfolio-upload-2" accept=".csv" />
|
<input type="file" id="portfolio-upload-2" accept=".csv" />
|
||||||
<div id="portfolio-status-2"></div>
|
<div id="portfolio-status-2"></div>
|
||||||
<button id="runComparison">Compare Portfolios</button>
|
<button id="runComparison">Compare Portfolios</button>
|
||||||
<div id="comparison-results" style="display: none;">
|
<div id="comparison-results" style="display: none;">
|
||||||
<h4>Comparison Results</h4>
|
<h4>Comparison Results</h4>
|
||||||
<p>Portfolio 1 AAL: <span id="aal-value-1">$0</span></p>
|
<p>Portfolio 1 AAL: <span id="aal-value-1">$0</span></p>
|
||||||
<p>Portfolio 2 AAL: <span id="aal-value-2">$0</span></p>
|
<p>Portfolio 2 AAL: <span id="aal-value-2">$0</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<h3>Weather Overlays</h3>
|
||||||
|
<div id="spc-controls">
|
||||||
|
<h4>SPC Outlook</h4>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="TSTM" checked> Thunderstorm</label>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="MRGL" checked> Marginal</label>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="SLGT" checked> Slight</label>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="ENH" checked> Enhanced</label>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="MDT" checked> Moderate</label>
|
||||||
|
<label><input type="checkbox" class="spc-checkbox" value="HIGH" checked> High</label>
|
||||||
|
</div>
|
||||||
|
<div id="alert-controls">
|
||||||
|
<h4>Weather Alerts</h4>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="tornado" checked> Tornado</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="thunderstorm" checked> Thunderstorm</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="flood" checked> Flood</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="wind" checked> Wind</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="snow_ice" checked> Snow/Ice</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="fog" checked> Fog</label>
|
||||||
|
<label><input type="checkbox" class="alert-checkbox" value="extreme_high_temperature" checked> Extreme Temp</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<script src="assets/cesium/Build/Cesium/Cesium.js"></script>
|
<script src="assets/cesium/Build/Cesium/Cesium.js"></script>
|
||||||
<script src="assets/js/wind.js?v=<?php echo time(); ?>"></script>
|
<script src="assets/js/wind.js?v=<?php echo time(); ?>"></script>
|
||||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user