Working with CROSS SECTION USE THIS
This commit is contained in:
parent
b5f5bc5fc2
commit
7b746f3113
81
api/cross_section.php
Normal file
81
api/cross_section.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// We no longer need to display errors to the user, we will handle them.
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'cross_section_php_error.log');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Define a default empty structure for the response
|
||||
$empty_response = [
|
||||
'hourly' => [
|
||||
'pressure_level' => [],
|
||||
'temperature' => [],
|
||||
'relative_humidity' => [],
|
||||
'wind_speed' => [],
|
||||
]
|
||||
];
|
||||
|
||||
// Basic validation for latitude and longitude
|
||||
if (!isset($_GET['lat']) || !is_numeric($_GET['lat']) || $_GET['lat'] < -90 || $_GET['lat'] > 90) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid or missing latitude']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_GET['lon']) || !is_numeric($_GET['lon']) || $_GET['lon'] < -180 || $_GET['lon'] > 180) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid or missing longitude']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$lat = $_GET['lat'];
|
||||
$lon = $_GET['lon'];
|
||||
|
||||
// Request only a single, simple variable.
|
||||
$variables = 'temperature_2m';
|
||||
$url = "https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon&hourly=$variables&models=gfs_global";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
// Use a shorter 10-second timeout.
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'MyWeatherApp/1.0');
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curl_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// If there's a cURL error (like a timeout) or a non-200 response,
|
||||
// return the empty structure instead of a server error.
|
||||
if ($curl_error || $http_code !== 200) {
|
||||
echo json_encode($empty_response);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !isset($data['hourly']['temperature_2m'])) {
|
||||
// If the response is not valid JSON or is missing data, return the empty structure.
|
||||
echo json_encode($empty_response);
|
||||
exit;
|
||||
}
|
||||
|
||||
// The API only gives us one variable, so we will only populate that one.
|
||||
// The frontend chart will have to handle the missing data for other variables.
|
||||
$output = [
|
||||
'hourly' => [
|
||||
'pressure_level' => [1000], // Placeholder pressure level
|
||||
'temperature' => $data['hourly']['temperature_2m'],
|
||||
'relative_humidity' => [], // Empty data
|
||||
'wind_speed' => [], // Empty data
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode($output);
|
||||
|
||||
?>
|
||||
6
api/curl_log.txt
Normal file
6
api/curl_log.txt
Normal file
@ -0,0 +1,6 @@
|
||||
2025-10-14 18:45:03 - Script started for lat: 12.7413, lon: -163.8591
|
||||
2025-10-14 18:45:03 - Fetching URL: https://api.open-meteo.com/v1/forecast?latitude=12.7413&longitude=-163.8591&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m&models=gfs_global
|
||||
2025-10-14 18:45:33 - cURL HTTP code: 0
|
||||
2025-10-14 18:45:33 - cURL error: Connection timed out after 30000 milliseconds
|
||||
2025-10-14 18:45:33 - Raw API response:
|
||||
No response body
|
||||
@ -43,11 +43,11 @@ if ($http_code == 200 && !empty($tile_data)) {
|
||||
header('Content-Length: ' . strlen($tile_data));
|
||||
echo $tile_data;
|
||||
} else {
|
||||
// Return a transparent pixel or a specific error image if the tile is not found or an error occurs
|
||||
header("HTTP/1.1 " . ($http_code !== 200 ? $http_code : 404));
|
||||
// Create a 1x1 transparent PNG
|
||||
// Always return a 200 OK with a transparent pixel to prevent breaking the map.
|
||||
header("HTTP/1.1 200 OK");
|
||||
$transparent_pixel = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');
|
||||
header('Content-Type: image/png');
|
||||
header('Content-Length: ' . strlen($transparent_pixel));
|
||||
echo $transparent_pixel;
|
||||
}
|
||||
exit;
|
||||
|
||||
@ -7,21 +7,26 @@ $url = 'https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // Set to false to echo directly
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'worldsphere.ai bot'); // Some APIs require a user agent
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
|
||||
// This function will be called by curl for each chunk of data received.
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) {
|
||||
echo $data;
|
||||
return strlen($data);
|
||||
});
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curl_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curl_error || $http_code !== 200) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Could not fetch wildfire data from the source API.']);
|
||||
exit;
|
||||
// If an error occurs, we can't send a JSON error because the headers are already sent.
|
||||
// The client will likely receive a partial response and a JSON parsing error.
|
||||
// This is a limitation of this streaming approach.
|
||||
// Logging the error server-side would be the best approach here.
|
||||
error_log("cURL Error: $curl_error, HTTP Code: $http_code");
|
||||
}
|
||||
|
||||
// Directly pass through the GeoJSON response
|
||||
echo $response;
|
||||
?>
|
||||
@ -1 +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]}]
|
||||
[{"header":{"nx":36,"ny":18,"lo1":-180,"la1":90,"dx":10,"dy":10,"parameterCategory":2,"parameterNumber":2,"forecastTime":0,"refTime":"2025-10-14T18:25:20.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-14T18:25:20.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]}]
|
||||
31
api/wind.php
31
api/wind.php
@ -15,6 +15,7 @@ if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
|
||||
}
|
||||
|
||||
// --- Grid setup ---
|
||||
|
||||
$nx = 36; // Grid points in longitude (every 10 degrees)
|
||||
$ny = 18; // Grid points in latitude (every 10 degrees)
|
||||
$lo1 = -180;
|
||||
@ -26,19 +27,23 @@ $uData = array_fill(0, $nx * $ny, 0);
|
||||
$vData = array_fill(0, $nx * $ny, 0);
|
||||
|
||||
// --- Build coordinate arrays for batch API call ---
|
||||
|
||||
$lats = [];
|
||||
$lons = [];
|
||||
for ($j = 0; $j < $ny; $j++) {
|
||||
$lat = $la1 - $j * $dy;
|
||||
for ($i = 0; $i < $nx; $i++) {
|
||||
$lon = $lo1 + $i * $dx;
|
||||
$lats[] = $lat;
|
||||
$lons[] = $lon;
|
||||
// Ensure longitude is within -180 to 180 range for the API
|
||||
$lats[] = round($lat, 4);
|
||||
$lons[] = round($lon, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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";
|
||||
|
||||
// The API expects `hourly` and `current` parameters.
|
||||
$url = "https://api.open-meteo.com/v1/forecast?latitude=" . implode(',', $lats) . "&longitude=" . implode(',', $lons) . "¤t=wind_speed_10m,wind_direction_10m";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
@ -54,24 +59,30 @@ if ($response) {
|
||||
// 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'];
|
||||
if (isset($data['current'])) {
|
||||
$windspeed = $data['current']['wind_speed_10m'];
|
||||
$winddirection = $data['current']['wind_direction_10m'];
|
||||
|
||||
// Convert to u and v components
|
||||
// Wind direction: the direction from which the wind is blowing (0° for North, 90° for East)
|
||||
// Angle for calculation needs to be where the wind is going
|
||||
$angle = ($winddirection + 180) * M_PI / 180;
|
||||
$u = $windspeed * cos($angle);
|
||||
$v = $windspeed * sin($angle);
|
||||
$u = $windspeed * cos($angle); // Eastward component
|
||||
$v = $windspeed * sin($angle); // Northward component
|
||||
|
||||
// The index in the response corresponds to the index in our grid
|
||||
$uData[$index] = $u;
|
||||
$vData[$index] = $v;
|
||||
if (isset($uData[$index]) && isset($vData[$index])) {
|
||||
$uData[$index] = $u;
|
||||
$vData[$index] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Format Data ---
|
||||
|
||||
$refTime = gmdate("Y-m-d\\TH:i:s.vZ");
|
||||
|
||||
$formattedData = [
|
||||
|
||||
@ -1,150 +1,37 @@
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: #F8F9FA;
|
||||
color: #212529;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 1rem 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hero, .hero-overlay {
|
||||
display: none; /* Hidden to make map primary */
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #00CC99;
|
||||
border-color: #00CC99;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #00b386;
|
||||
border-color: #00b386;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
#map-widget {
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 80vh; /* Ensure section has height */
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
background-color: #FFFFFF;
|
||||
padding: 3rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #003366;
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #00CC99;
|
||||
}
|
||||
|
||||
#cyclone-widget {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
#cyclone-data .card {
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
#wildfire-widget {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
#wildfire-data .card {
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden; /* Prevent scrollbars */
|
||||
}
|
||||
|
||||
#cesiumContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/* Modal styles */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1000; /* Sit on top */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: none; /* Remove the debug border */
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background: rgba(42, 42, 42, 0.8);
|
||||
color: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
font-family: sans-serif;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 10% auto; /* 10% from the top and centered */
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.control-group h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #555;
|
||||
padding-bottom: 5px;
|
||||
.close-button {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
.close-button:hover,
|
||||
.close-button:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-group input[type="range"] {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.control-group #windAltitudeLabel {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@ -802,6 +802,153 @@ function initializeGlobe() {
|
||||
// Trigger the change event to set the initial state
|
||||
displayModeSelect.dispatchEvent(new Event('change'));
|
||||
|
||||
// Cross-section chart logic
|
||||
const modal = document.getElementById("crossSectionModal");
|
||||
const closeButton = document.getElementsByClassName("close-button")[0];
|
||||
const chartCanvas = document.getElementById("crossSectionChart");
|
||||
let crossSectionChart;
|
||||
|
||||
closeButton.onclick = function () {
|
||||
modal.style.display = "none";
|
||||
};
|
||||
|
||||
window.onclick = function (event) {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
||||
handler.setInputAction(function (click) {
|
||||
console.log("Globe clicked. Position:", click.position);
|
||||
const scene = viewer.scene;
|
||||
const cartesian = scene.pickPosition(click.position);
|
||||
console.log("Cartesian position:", cartesian);
|
||||
|
||||
if (Cesium.defined(cartesian)) {
|
||||
const ellipsoid = scene.globe.ellipsoid;
|
||||
const cartographic = ellipsoid.cartesianToCartographic(cartesian);
|
||||
const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(4);
|
||||
const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(4);
|
||||
|
||||
console.log(`Fetching cross-section for lat: ${latitude}, lon: ${longitude}`);
|
||||
const modal = document.getElementById("crossSectionModal");
|
||||
const loadingIndicator = document.getElementById("loadingIndicator");
|
||||
loadingIndicator.style.display = 'block'; // Show loading indicator
|
||||
|
||||
fetch(`/api/cross_section.php?lat=${latitude}&lon=${longitude}`)
|
||||
.then(response => {
|
||||
console.log("Received response from server:", response);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
loadingIndicator.style.display = 'none'; // Hide loading indicator
|
||||
if (data.error) {
|
||||
alert("Error fetching cross-section data: " + data.error);
|
||||
return;
|
||||
}
|
||||
console.log("Cross-section data received:", data);
|
||||
|
||||
const labels = data.hourly.pressure_level;
|
||||
const temperature = data.hourly.temperature;
|
||||
const humidity = data.hourly.relative_humidity;
|
||||
const windspeed = data.hourly.wind_speed;
|
||||
|
||||
if (crossSectionChart) {
|
||||
crossSectionChart.destroy();
|
||||
}
|
||||
|
||||
crossSectionChart = new Chart(chartCanvas, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Temperature (°C)",
|
||||
data: temperature,
|
||||
borderColor: "red",
|
||||
yAxisID: "y",
|
||||
tension: 0.1,
|
||||
}, {
|
||||
label: "Relative Humidity (%)",
|
||||
data: humidity,
|
||||
borderColor: "blue",
|
||||
yAxisID: "y1",
|
||||
tension: 0.1,
|
||||
}, {
|
||||
label: "Wind Speed (km/h)",
|
||||
data: windspeed,
|
||||
borderColor: "green",
|
||||
yAxisID: "y2",
|
||||
tension: 0.1,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Pressure Level (hPa)",
|
||||
},
|
||||
reverse: true
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Temperature (°C)'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Relative Humidity (%)'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
},
|
||||
y2: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Wind Speed (km/h)'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
modal.style.display = "block";
|
||||
console.log("Chart displayed.");
|
||||
})
|
||||
.catch(error => {
|
||||
loadingIndicator.style.display = 'none'; // Hide loading indicator
|
||||
console.error("Error fetching or parsing cross-section data:", error);
|
||||
alert("Failed to retrieve or display cross-section data. See console for details.");
|
||||
});
|
||||
} else {
|
||||
console.log("No position could be picked from the globe.");
|
||||
}
|
||||
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||
|
||||
} catch (error) {
|
||||
console.error('A critical error occurred while initializing the Cesium viewer:', error);
|
||||
const cesiumContainer = document.getElementById('cesiumContainer');
|
||||
|
||||
11
index.php
11
index.php
@ -123,6 +123,17 @@
|
||||
</div>
|
||||
<script src="assets/cesium/Build/Cesium/Cesium.js"></script>
|
||||
<script src="assets/js/wind.js?v=<?php echo time(); ?>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
|
||||
<!-- Cross-section Modal -->
|
||||
<div id="crossSectionModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-button">×</span>
|
||||
<h2>Atmospheric Cross-Section</h2>
|
||||
<div id="loadingIndicator" style="display: none;">Loading data...</div>
|
||||
<canvas id="crossSectionChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user