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));
|
header('Content-Length: ' . strlen($tile_data));
|
||||||
echo $tile_data;
|
echo $tile_data;
|
||||||
} else {
|
} else {
|
||||||
// Return a transparent pixel or a specific error image if the tile is not found or an error occurs
|
// Always return a 200 OK with a transparent pixel to prevent breaking the map.
|
||||||
header("HTTP/1.1 " . ($http_code !== 200 ? $http_code : 404));
|
header("HTTP/1.1 200 OK");
|
||||||
// Create a 1x1 transparent PNG
|
|
||||||
$transparent_pixel = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');
|
$transparent_pixel = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');
|
||||||
header('Content-Type: image/png');
|
header('Content-Type: image/png');
|
||||||
|
header('Content-Length: ' . strlen($transparent_pixel));
|
||||||
echo $transparent_pixel;
|
echo $transparent_pixel;
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@ -7,21 +7,26 @@ $url = 'https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS
|
|||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
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_USERAGENT, 'worldsphere.ai bot'); // Some APIs require a user agent
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
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);
|
$response = curl_exec($ch);
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$curl_error = curl_error($ch);
|
$curl_error = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($curl_error || $http_code !== 200) {
|
if ($curl_error || $http_code !== 200) {
|
||||||
http_response_code(500);
|
// If an error occurs, we can't send a JSON error because the headers are already sent.
|
||||||
echo json_encode(['error' => 'Could not fetch wildfire data from the source API.']);
|
// The client will likely receive a partial response and a JSON parsing error.
|
||||||
exit;
|
// 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]}]
|
||||||
33
api/wind.php
33
api/wind.php
@ -15,6 +15,7 @@ if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Grid setup ---
|
// --- Grid setup ---
|
||||||
|
|
||||||
$nx = 36; // Grid points in longitude (every 10 degrees)
|
$nx = 36; // Grid points in longitude (every 10 degrees)
|
||||||
$ny = 18; // Grid points in latitude (every 10 degrees)
|
$ny = 18; // Grid points in latitude (every 10 degrees)
|
||||||
$lo1 = -180;
|
$lo1 = -180;
|
||||||
@ -26,19 +27,23 @@ $uData = array_fill(0, $nx * $ny, 0);
|
|||||||
$vData = array_fill(0, $nx * $ny, 0);
|
$vData = array_fill(0, $nx * $ny, 0);
|
||||||
|
|
||||||
// --- Build coordinate arrays for batch API call ---
|
// --- Build coordinate arrays for batch API call ---
|
||||||
|
|
||||||
$lats = [];
|
$lats = [];
|
||||||
$lons = [];
|
$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;
|
||||||
$lats[] = $lat;
|
// Ensure longitude is within -180 to 180 range for the API
|
||||||
$lons[] = $lon;
|
$lats[] = round($lat, 4);
|
||||||
|
$lons[] = round($lon, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Fetch data from Open-Meteo in a single call ---
|
// --- 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();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
@ -54,24 +59,30 @@ if ($response) {
|
|||||||
// The API returns an array of results, one for each coordinate pair
|
// The API returns an array of results, one for each coordinate pair
|
||||||
if (is_array($results)) {
|
if (is_array($results)) {
|
||||||
foreach ($results as $index => $data) {
|
foreach ($results as $index => $data) {
|
||||||
if (isset($data['current_weather'])) {
|
if (isset($data['current'])) {
|
||||||
$windspeed = $data['current_weather']['windspeed'];
|
$windspeed = $data['current']['wind_speed_10m'];
|
||||||
$winddirection = $data['current_weather']['winddirection'];
|
$winddirection = $data['current']['wind_direction_10m'];
|
||||||
|
|
||||||
// Convert to u and v components
|
// 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;
|
$angle = ($winddirection + 180) * M_PI / 180;
|
||||||
$u = $windspeed * cos($angle);
|
$u = $windspeed * cos($angle); // Eastward component
|
||||||
$v = $windspeed * sin($angle);
|
$v = $windspeed * sin($angle); // Northward component
|
||||||
|
|
||||||
// The index in the response corresponds to the index in our grid
|
// The index in the response corresponds to the index in our grid
|
||||||
$uData[$index] = $u;
|
if (isset($uData[$index]) && isset($vData[$index])) {
|
||||||
$vData[$index] = $v;
|
$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.vZ");
|
||||||
|
|
||||||
$formattedData = [
|
$formattedData = [
|
||||||
@ -115,4 +126,4 @@ file_put_contents($cacheFile, $json_data);
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo $json_data;
|
echo $json_data;
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@ -1,150 +1,37 @@
|
|||||||
body {
|
/* Modal styles */
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
.modal {
|
||||||
background-color: #F8F9FA;
|
display: none; /* Hidden by default */
|
||||||
color: #212529;
|
position: fixed; /* Stay in place */
|
||||||
display: flex;
|
z-index: 1000; /* Sit on top */
|
||||||
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;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
top: 0;
|
||||||
bottom: 0;
|
width: 100%; /* Full width */
|
||||||
border: none; /* Remove the debug border */
|
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 {
|
.modal-content {
|
||||||
position: absolute;
|
background-color: #fefefe;
|
||||||
top: 20px;
|
margin: 10% auto; /* 10% from the top and centered */
|
||||||
left: 20px;
|
padding: 20px;
|
||||||
background: rgba(42, 42, 42, 0.8);
|
border: 1px solid #888;
|
||||||
color: #fff;
|
width: 80%;
|
||||||
padding: 15px;
|
max-width: 800px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
font-family: sans-serif;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-group h3 {
|
.close-button {
|
||||||
margin-top: 0;
|
color: #aaa;
|
||||||
margin-bottom: 10px;
|
float: right;
|
||||||
font-size: 16px;
|
font-size: 28px;
|
||||||
border-bottom: 1px solid #555;
|
font-weight: bold;
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-group label {
|
.close-button:hover,
|
||||||
display: block;
|
.close-button:focus {
|
||||||
margin-bottom: 5px;
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
cursor: pointer;
|
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
|
// Trigger the change event to set the initial state
|
||||||
displayModeSelect.dispatchEvent(new Event('change'));
|
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) {
|
} 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');
|
||||||
|
|||||||
11
index.php
11
index.php
@ -123,6 +123,17 @@
|
|||||||
</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="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user