diff --git a/.user.ini b/.user.ini
new file mode 100644
index 0000000..69b3887
--- /dev/null
+++ b/.user.ini
@@ -0,0 +1 @@
+memory_limit = 512M
diff --git a/api/cyclone.php b/api/cyclone.php
deleted file mode 100644
index 6eaa9a5..0000000
--- a/api/cyclone.php
+++ /dev/null
@@ -1,43 +0,0 @@
- 'Failed to fetch SPC data.']);
- exit;
-}
-
-// Parse the XML
-$xml = @simplexml_load_string($rss);
-
-if ($xml === false) {
- echo json_encode(['error' => 'Failed to parse SPC XML.']);
- exit;
-}
-
-$alerts = [];
-if (isset($xml->channel->item)) {
- foreach ($xml->channel->item as $item) {
- // The title often contains the most succinct information
- $title = (string)$item->title;
-
- // The description can be long, let's create a summary or just use the title
- $description = (string)$item->description;
-
- $alerts[] = [
- 'headline' => $title,
- 'description' => strip_tags($description), // Basic sanitization
- 'link' => (string)$item->link
- ];
- }
-}
-
-// Return the alerts as JSON
-echo json_encode($alerts);
-?>
\ No newline at end of file
diff --git a/api/gfs.php b/api/gfs.php
new file mode 100644
index 0000000..64c57b1
--- /dev/null
+++ b/api/gfs.php
@@ -0,0 +1,91 @@
+ 'Failed to download GFS file', 'http_code' => $http_code]);
+ exit;
+ }
+}
+
+// --- Convert GRIB to XYZ using gdal_translate ---
+// We'll use band 1 for this example. You can use gdalinfo to see available bands.
+$gdal_command = 'gdal_translate -b 1 -of XYZ ' . escapeshellarg($grib_file) . ' ' . escapeshellarg($xyz_file);
+$gdal_output = shell_exec($gdal_command);
+
+if (!file_exists($xyz_file)) {
+ echo json_encode(['error' => 'Failed to convert GRIB to XYZ', 'gdal_output' => $gdal_output]);
+ unlink($grib_file);
+ exit;
+}
+
+// --- Read the XYZ file and create GeoJSON features ---
+$features = [];
+$handle = fopen($xyz_file, 'r');
+$line_count = 0;
+$sample_rate = 100; // Process 1 in every 100 lines
+
+if ($handle) {
+ while (($line = fgets($handle)) !== false) {
+ $line_count++;
+ if ($line_count % $sample_rate !== 0) {
+ continue;
+ }
+
+ $parts = preg_split('/\s+/', trim($line));
+ if (count($parts) === 3) {
+ $lon = floatval($parts[0]);
+ $lat = floatval($parts[1]);
+ $value = floatval($parts[2]);
+
+ // Skip points that are exactly zero, often represent no data
+ if ($value === 0.0) {
+ continue;
+ }
+
+ $features[] = [
+ 'type' => 'Feature',
+ 'properties' => ['value' => $value],
+ 'geometry' => [
+ 'type' => 'Point',
+ 'coordinates' => [$lon, $lat]
+ ]
+ ];
+ }
+ }
+ fclose($handle);
+}
+
+// --- Clean up temporary files ---
+unlink($grib_file);
+unlink($xyz_file);
+
+// --- Output the GeoJSON ---
+echo json_encode([
+ 'type' => 'FeatureCollection',
+ 'features' => $features
+]);
+
+?>
\ No newline at end of file
diff --git a/api/hurricanes.php b/api/hurricanes.php
new file mode 100644
index 0000000..7186da6
--- /dev/null
+++ b/api/hurricanes.php
@@ -0,0 +1,118 @@
+ 'Failed to fetch NHC data.']);
+ exit;
+}
+
+// Save the data to the temporary file
+file_put_contents($tmp_kmz_file, $kmz_data);
+
+if (!class_exists('ZipArchive')) {
+ echo json_encode(['error' => 'ZipArchive class does not exist.']);
+ exit;
+}
+
+// Use ZipArchive to open the KMZ file
+$zip = new ZipArchive;
+if ($zip->open($tmp_kmz_file) === TRUE) {
+ // NHC KMZ files typically contain a single KML file, often named doc.kml or similar.
+ // We will look for the first .kml file in the archive.
+ $kml_content = false;
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $filename = $zip->getNameIndex($i);
+ if (strtolower(substr($filename, -4)) === '.kml') {
+ $kml_content = $zip->getFromIndex($i);
+ break;
+ }
+ }
+ $zip->close();
+
+ if ($kml_content === false) {
+ echo json_encode(['error' => 'KML file not found in the KMZ archive.']);
+ exit;
+ }
+
+ // Parse the KML content
+ $xml = simplexml_load_string($kml_content, "SimpleXMLElement", LIBXML_NOCDATA);
+ if ($xml === false) {
+ echo json_encode(['error' => 'Failed to parse KML data.']);
+ exit;
+ }
+
+ // Register the KML namespace
+ $xml->registerXPathNamespace('kml', 'http://www.opengis.net/kml/2.2');
+
+ $features = [];
+
+ // Find all Placemarks in the KML
+ foreach ($xml->xpath('//kml:Placemark') as $placemark) {
+ $placemark->registerXPathNamespace('kml', 'http://www.opengis.net/kml/2.2');
+ $name = (string)$placemark->name;
+
+ // Look for Polygon
+ $polygon = $placemark->xpath('.//kml:Polygon');
+ if ($polygon && isset($polygon[0]->outerBoundaryIs->LinearRing->coordinates)) {
+ $coordinates_str = (string)$polygon[0]->outerBoundaryIs->LinearRing->coordinates;
+ $coordinates = parse_coordinates($coordinates_str);
+ if (!empty($coordinates)) {
+ $features[] = [
+ 'name' => $name,
+ 'type' => 'Polygon',
+ 'coordinates' => $coordinates
+ ];
+ }
+ }
+
+ // Look for LineString
+ $linestring = $placemark->xpath('.//kml:LineString');
+ if ($linestring && isset($linestring[0]->coordinates)) {
+ $coordinates_str = (string)$linestring[0]->coordinates;
+ $coordinates = parse_coordinates($coordinates_str);
+ if (!empty($coordinates)) {
+ $features[] = [
+ 'name' => $name,
+ 'type' => 'LineString',
+ 'coordinates' => $coordinates
+ ];
+ }
+ }
+ }
+
+ echo json_encode($features);
+
+} else {
+ echo json_encode(['error' => 'Failed to open KMZ file.']);
+}
+
+// Clean up the temporary file
+unlink($tmp_kmz_file);
+
+function parse_coordinates($coordinates_str) {
+ $coords = [];
+ $pairs = explode(' ', trim($coordinates_str));
+ foreach ($pairs as $pair) {
+ $parts = explode(',', $pair);
+ if (count($parts) >= 2) {
+ $lon = floatval($parts[0]);
+ $lat = floatval($parts[1]);
+ // Ensure coordinates are valid
+ if (is_finite($lat) && is_finite($lon)) {
+ $coords[] = $lon;
+ $coords[] = $lat;
+ }
+ }
+ }
+ return $coords;
+}
+?>
\ No newline at end of file
diff --git a/api/spc.php b/api/spc.php
new file mode 100644
index 0000000..37434e5
--- /dev/null
+++ b/api/spc.php
@@ -0,0 +1,66 @@
+ 'Failed to fetch SPC data.']);
+ exit;
+}
+
+// Parse the KML content
+$xml = simplexml_load_string($kml_content, "SimpleXMLElement", LIBXML_NOCDATA);
+if ($xml === false) {
+ echo json_encode(['error' => 'Failed to parse KML data.']);
+ exit;
+}
+
+// Register the KML namespace
+$xml->registerXPathNamespace('kml', 'http://www.opengis.net/kml/2.2');
+
+$features = [];
+
+// Find all Placemarks in the KML
+foreach ($xml->xpath('//kml:Placemark') as $placemark) {
+ $placemark->registerXPathNamespace('kml', 'http://www.opengis.net/kml/2.2');
+ $name = (string)$placemark->name;
+
+ // Look for Polygon
+ $polygon = $placemark->xpath('.//kml:Polygon');
+ if ($polygon && isset($polygon[0]->outerBoundaryIs->LinearRing->coordinates)) {
+ $coordinates_str = (string)$polygon[0]->outerBoundaryIs->LinearRing->coordinates;
+ $coordinates = parse_coordinates($coordinates_str);
+ if (!empty($coordinates)) {
+ $features[] = [
+ 'name' => $name,
+ 'type' => 'Polygon',
+ 'coordinates' => $coordinates
+ ];
+ }
+ }
+}
+
+echo json_encode($features);
+
+function parse_coordinates($coordinates_str) {
+ $coords = [];
+ $pairs = explode(' ', trim($coordinates_str));
+ foreach ($pairs as $pair) {
+ $parts = explode(',', $pair);
+ if (count($parts) >= 2) {
+ $lon = floatval($parts[0]);
+ $lat = floatval($parts[1]);
+ // Ensure coordinates are valid
+ if (is_finite($lat) && is_finite($lon)) {
+ $coords[] = $lon;
+ $coords[] = $lat;
+ }
+ }
+ }
+ return $coords;
+}
+?>
\ No newline at end of file
diff --git a/api/wildfires.php b/api/wildfires.php
index b59c433..2997d3d 100644
--- a/api/wildfires.php
+++ b/api/wildfires.php
@@ -17,25 +17,6 @@ if ($response === false) {
exit;
}
-$data = json_decode($response, true);
-
-if (json_last_error() !== JSON_ERROR_NONE) {
- echo json_encode(['error' => 'Could not parse wildfire data.']);
- exit;
-}
-
-$features = $data['features'] ?? [];
-
-$wildfires = [];
-foreach ($features as $feature) {
- $properties = $feature['properties'];
- $wildfires[] = [
- 'name' => $properties['poly_IncidentName'],
- 'acres' => $properties['poly_Acres_AutoCalc'],
- 'started' => $properties['attr_InitialResponseDateTime'],
- 'percent_contained' => $properties['attr_PercentContained'],
- ];
-}
-
-echo json_encode($wildfires);
+// Directly pass through the GeoJSON response
+echo $response;
?>
\ No newline at end of file
diff --git a/assets/css/custom.css b/assets/css/custom.css
index b7e55af..1cccb0a 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,44 +1,26 @@
-
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 {
- position: relative;
- padding: 8rem 0;
- background-size: cover;
- background-position: center;
- color: white;
-}
-
-.hero-overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 25, 51, 0.7);
- z-index: 1;
-}
-
-.hero .container {
- position: relative;
- z-index: 2;
-}
-
-.hero h1 {
- font-size: 3.5rem;
- font-weight: 700;
-}
-
-.hero p {
- font-size: 1.25rem;
+.hero, .hero-overlay {
+ display: none; /* Hidden to make map primary */
}
.btn-primary {
@@ -58,6 +40,14 @@ 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;
@@ -80,6 +70,7 @@ h2 {
background-color: #003366;
color: white;
padding: 3rem 0;
+ flex-shrink: 0;
}
.footer a {
@@ -101,3 +92,20 @@ h2 {
#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;
+ right: 0;
+ bottom: 0;
+ border: none; /* Remove the debug border */
+}
\ No newline at end of file
diff --git a/assets/js/main.js b/assets/js/main.js
index a53341e..7acf020 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,104 +1,119 @@
-
document.addEventListener('DOMContentLoaded', function () {
- const contactForm = document.getElementById('contactForm');
- if (contactForm) {
- contactForm.addEventListener('submit', function (e) {
- e.preventDefault();
- const form = e.target;
- const formData = new FormData(form);
- const status = document.getElementById('form-status');
+ console.log('DOM fully loaded and parsed');
+ // Set your Cesium Ion default access token
+ Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjZTY0ZTQ1Yi0zYmYxLTQ5MjItODdkOS05ZDY0ZGRjYjQwM2QiLCJpZCI6MjA5ODgwLCJpYXQiOjE3MTM4MTY3OTB9.A-3Jt_G0K81s-A-XLpT2bn5aY2H3s-n2p-2jYf-i-g';
- // Basic client-side validation
- const name = formData.get('name');
- const email = formData.get('email');
- const message = formData.get('message');
-
- if (!name || !email || !message) {
- status.innerHTML = '
Please fill out all fields.
';
- return;
- }
-
- fetch('contact.php', {
- method: 'POST',
- body: formData
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- status.innerHTML = `${data.message}
`;
- form.reset();
- } else {
- status.innerHTML = `${data.message}
`;
- }
- })
- .catch(error => {
- status.innerHTML = 'An error occurred. Please try again.
';
- console.error('Error:', error);
- });
+ try {
+ console.log('Initializing Cesium Viewer');
+ // Initialize a Cesium Viewer with a map
+ const viewer = new Cesium.Viewer('cesiumContainer', {
+ imageryProvider: new Cesium.OpenStreetMapImageryProvider({
+ url : 'https://a.tile.openstreetmap.org/'
+ }),
+ animation: false,
+ baseLayerPicker: false,
+ fullscreenButton: false,
+ geocoder: false,
+ homeButton: false,
+ infoBox: true,
+ sceneModePicker: false,
+ selectionIndicator: false,
+ timeline: false,
+ navigationHelpButton: false,
+ scene3DOnly: true
});
- }
+ viewer.scene.globe.depthTestAgainstTerrain = false;
+ console.log('Cesium Viewer initialized successfully');
- // Fetch and display cyclone data
- const cycloneDataContainer = document.getElementById('cyclone-data');
- if (cycloneDataContainer) {
- fetch('api/cyclone.php')
- .then(response => response.json())
- .then(data => {
- if (data && data.length > 0) {
- let html = '';
- data.forEach(alert => {
- html += `
-
-
-
-
${alert.headline}
-
${alert.description.substring(0, 150)}...
-
Read More
-
-
-
- `;
- });
- cycloneDataContainer.innerHTML = html;
- } else {
- cycloneDataContainer.innerHTML = 'No active severe weather alerts from the SPC at the moment.
';
- }
- })
- .catch(error => {
- cycloneDataContainer.innerHTML = 'Could not load cyclone data. Please try again later.
';
- console.error('Error fetching cyclone data:', error);
- });
- }
+
- // Fetch and display wildfire data
- const wildfireDataContainer = document.getElementById('wildfire-data');
- if (wildfireDataContainer) {
- fetch('api/wildfires.php')
- .then(response => response.json())
- .then(data => {
- if (data && data.length > 0) {
- let html = '';
- data.forEach(fire => {
- html += `
-
-
-
-
${fire.name}
-
${fire.acres ? Math.round(fire.acres) + ' acres' : 'Size not available'}
-
${fire.percent_contained !== null ? fire.percent_contained + '% contained' : 'Containment not available'}
-
-
-
- `;
- });
- wildfireDataContainer.innerHTML = html;
- } else {
- wildfireDataContainer.innerHTML = 'No active wildfires reported at the moment.
';
+ // Function to load wildfire data
+ const loadWildfireData = async () => {
+ try {
+ console.log('Fetching wildfire data...');
+ const response = await fetch('api/wildfires.php');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
}
- })
- .catch(error => {
- wildfireDataContainer.innerHTML = 'Could not load wildfire data. Please try again later.
';
- console.error('Error fetching wildfire data:', error);
- });
+ const geojsonData = await response.json();
+ console.log('Wildfire data fetched successfully.');
+
+ const wildfireDataSource = new Cesium.GeoJsonDataSource();
+ await wildfireDataSource.load(geojsonData, {
+ stroke: Cesium.Color.RED,
+ fill: Cesium.Color.RED.withAlpha(0.5),
+ strokeWidth: 2
+ });
+
+ viewer.dataSources.add(wildfireDataSource);
+ console.log('Wildfire data source added to viewer.');
+
+ } catch (error) {
+ console.error('Error loading wildfire data:', error);
+ // This catch block prevents the globe from crashing if the API fails.
+ }
+ };
+
+ // Function to load hurricane data
+ const loadHurricaneData = async () => {
+ try {
+ console.log('Fetching hurricane data...');
+ // Use KmlDataSource for the KML data from the hurricanes API
+ const hurricaneDataSource = await Cesium.KmlDataSource.load('api/hurricanes.php', {
+ camera: viewer.camera,
+ canvas: viewer.canvas
+ });
+ await viewer.dataSources.add(hurricaneDataSource);
+ console.log('Hurricane data source added to viewer.');
+ } catch (error) {
+ console.error('Error loading hurricane data:', error);
+ }
+ };
+
+ // Function to load GFS data
+ const loadGfsData = async () => {
+ try {
+ console.log('Fetching GFS data...');
+ const response = await fetch('api/gfs.php');
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const geojsonData = await response.json();
+ console.log('GFS data fetched successfully.');
+
+ const gfsDataSource = new Cesium.GeoJsonDataSource();
+ await gfsDataSource.load(geojsonData, {
+ stroke: Cesium.Color.BLUE.withAlpha(0.3),
+ fill: Cesium.Color.BLUE.withAlpha(0.3),
+ strokeWidth: 1
+ });
+
+ // Simple heatmap-like styling for GFS points
+ gfsDataSource.entities.values.forEach(entity => {
+ const value = entity.properties.value.getValue();
+ const color = Cesium.Color.fromHsl(0.6 - (value - 100000) / 2000 * 0.5, 1.0, 0.5).withAlpha(0.5);
+ entity.point = new Cesium.PointGraphics({
+ color: color,
+ pixelSize: 5
+ });
+ });
+
+ viewer.dataSources.add(gfsDataSource);
+ console.log('GFS data source added to viewer.');
+
+ } catch (error) {
+ console.error('Error loading GFS data:', error);
+ }
+ };
+
+ // Load all data sources
+ loadWildfireData();
+ // loadHurricaneData();
+ // loadGfsData();
+
+ } catch (error) {
+ console.error('A critical error occurred while initializing the Cesium viewer:', error);
+ const cesiumContainer = document.getElementById('cesiumContainer');
+ cesiumContainer.innerHTML = 'Error: Could not load the 3D scene. Please check the console for details.
';
}
-});
+});
\ No newline at end of file
diff --git a/assets/pasted-20251014-012612-96ac69da.png b/assets/pasted-20251014-012612-96ac69da.png
new file mode 100644
index 0000000..e041336
Binary files /dev/null and b/assets/pasted-20251014-012612-96ac69da.png differ
diff --git a/index.php b/index.php
index 69b02b3..9852a1e 100644
--- a/index.php
+++ b/index.php
@@ -1,162 +1,15 @@
-
- Worldsphere.ai - AI-Powered Weather Prediction
-
-
-
-
-
-
-
-
-
+ Worldsphere.ai - 3D Weather Map
+
-
-
-
-
-
-
-
-
-
-
-
First Beta Version now available! Download now
-
Revolutionizing Climate Resilience with AI-Powered Weather Prediction
-
Where Cutting-Edge Technology Meets Climate Action. At Worldsphere.ai, we’re harnessing the power of artificial intelligence to transform how we predict, prepare for, and respond to extreme weather events.
-
Join the Climate Tech Revolution
-
-
-
-
-
- Active Tropical Cyclones
-
-
Loading real-time cyclone data...
-
-
-
-
- Active Wildfires
-
-
Loading real-time wildfire data...
-
-
-
-
- Technology & Innovation
- Our innovative platform combines state-of-the-art AI models, big data analytics, and immersive visualization techniques to provide unparalleled insights into weather risks and climate patterns.
-
-
-
-
AI-Powered Hurricane Prediction
-
Our flagship technology utilizes advanced diffusion models to revolutionize hurricane forecasting.
-
-
-
-
-
Satellite-to-Wind Technology
-
Generate realistic 2D wind fields from infrared satellite imagery and automatically remove land masses.
-
-
-
-
-
Model Interpretability
-
We bridge traditional meteorological frameworks and AI-generated insights by mapping atmospheric patterns to model activations.
-
-
-
-
-
-
-
-
-
-
Our Mission
-
Empowering Global Resilience in the Face of Climate Change.
-
We recognize the urgency of the climate crisis. At Worldsphere.ai, we’re committed to developing solutions that address both current and future climate challenges, from severe droughts to devastating floods. Our ultimate goal is to create a world where every individual, community, and organization has access to accurate, timely, and actionable weather intelligence.
-
-
-
Our Core Objectives
-
- Advance the field of Al-powered weather prediction
- Improve early warning systems for extreme weather events
- Support businesses and communities in climate risk management
- Foster collaboration between technology and climate science
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file