// Set your Cesium Ion default access token immediately Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjZTY0ZTQ1Yi0zYmYxLTQ5MjItODdkOS05ZDY0ZGRjYjQwM2QiLCJpZCI6MjA5ODgwLCJpYXQiOjE3MTM4MTY3OTB9.A-3Jt_G0K81s-A-XLpT2bn5aY2H3s-n2p-2jYf-i-g'; function initializeGlobe() { let epCurveChart = null; // To hold the chart instance console.log('Cesium is defined, initializing globe.'); try { console.log('Initializing Cesium Viewer'); 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'); // Add Weather Layer const weatherImageryProvider = new Cesium.UrlTemplateImageryProvider({ url: `api/weather.php?layer=clouds_new&z={z}&x={x}&y={y}`, credit: 'Weather data © OpenWeatherMap' }); const weatherLayer = viewer.imageryLayers.addImageryProvider(weatherImageryProvider); weatherLayer.alpha = 0.6; // UI Controls const weatherCheckbox = document.getElementById('weatherLayerCheckbox'); weatherCheckbox.addEventListener('change', function() { weatherLayer.show = this.checked; }); let wildfireDataSource; const wildfireCheckbox = document.getElementById('wildfireLayerCheckbox'); wildfireCheckbox.addEventListener('change', function() { if (wildfireDataSource) { wildfireDataSource.show = this.checked; } }); let spcDataSource = new Cesium.CustomDataSource('spcOutlook'); viewer.dataSources.add(spcDataSource); const spcCheckbox = document.getElementById('spcLayerCheckbox'); spcCheckbox.addEventListener('change', function() { spcDataSource.show = this.checked; }); let weatherAlertsDataSource = new Cesium.CustomDataSource('weatherAlerts'); viewer.dataSources.add(weatherAlertsDataSource); const weatherAlertsCheckbox = document.getElementById('weatherAlertsLayerCheckbox'); weatherAlertsCheckbox.addEventListener('change', function() { weatherAlertsDataSource.show = this.checked; }); // Wind Layer const windLayer = new WindLayer(viewer, { particleHeight: 10000, particleCount: 10000, maxAge: 120, particleSpeed: 5 }); const windCheckbox = document.getElementById('windLayerCheckbox'); windCheckbox.addEventListener('change', function() { windLayer.setVisible(this.checked); }); const windAltitudeSlider = document.getElementById('windAltitudeSlider'); const windAltitudeLabel = document.getElementById('windAltitudeLabel'); windAltitudeSlider.addEventListener('input', function() { const altitude = parseInt(this.value, 10); windAltitudeLabel.textContent = `${altitude} m`; windLayer.setOptions({ particleHeight: altitude }); }); const playPauseButton = document.getElementById('playPauseWind'); let isPlaying = true; playPauseButton.addEventListener('click', function() { if (isPlaying) { windLayer.pause(); this.textContent = 'Play'; } else { windLayer.play(); this.textContent = 'Pause'; } isPlaying = !isPlaying; }); const particleDensitySlider = document.getElementById('particleDensitySlider'); particleDensitySlider.addEventListener('input', function() { const density = parseFloat(this.value); windLayer.setParticleDensity(density); }); // 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}`); } const geojsonData = await response.json(); console.log('Wildfire data fetched successfully.'); 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); } }; const loadSpcData = async () => { try { console.log('Fetching SPC data...'); const response = await fetch('api/spc.php'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const spcData = await response.json(); console.log('SPC data fetched successfully.'); spcDataSource.entities.removeAll(); const spcColors = { 'TSTM': Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.5), // General Thunderstorms 'MRGL': Cesium.Color.fromCssColorString('#00C800').withAlpha(0.5), // Marginal 'SLGT': Cesium.Color.fromCssColorString('#FFFF00').withAlpha(0.5), // Slight 'ENH': Cesium.Color.fromCssColorString('#FFA500').withAlpha(0.5), // Enhanced 'MDT': Cesium.Color.fromCssColorString('#FF0000').withAlpha(0.5), // Moderate 'HIGH': Cesium.Color.fromCssColorString('#FF00FF').withAlpha(0.5) // High }; spcData.forEach(feature => { const color = spcColors[feature.name] || Cesium.Color.GRAY.withAlpha(0.5); spcDataSource.entities.add({ name: `SPC Outlook: ${feature.name}`, polygon: { hierarchy: Cesium.Cartesian3.fromDegreesArray(feature.coordinates), material: color, outline: true, outlineColor: Cesium.Color.BLACK } }); }); console.log('SPC data source updated.'); } catch (error) { console.error('Error loading SPC data:', error); } }; const loadWeatherAlerts = async () => { try { console.log('Fetching weather alerts...'); const response = await fetch('api/weather_alerts.php'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const alertsData = await response.json(); console.log('Weather alerts fetched successfully.'); weatherAlertsDataSource.entities.removeAll(); if (alertsData.alerts) { alertsData.alerts.forEach(alert => { const alertColor = Cesium.Color.ORANGE.withAlpha(0.5); // The API provides polygons, so we need to handle them if (alert.geometry && alert.geometry.type === 'Polygon') { 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 } }); } }); } console.log('Weather alerts data source updated.'); } catch (error) { console.error('Error loading weather alerts:', error); } }; // Load all data sources loadWildfireData(); loadSpcData(); loadWeatherAlerts(); loadHurricaneSelector(); // --- CAT Simulation Logic --- const catSimulationDataSource = new Cesium.CustomDataSource('catSimulation'); viewer.dataSources.add(catSimulationDataSource); let customPortfolioData = null; const portfolio = [ { id: 1, name: 'Miami Property', lat: 25.7617, lon: -80.1918, value: 1000000 }, { id: 2, name: 'Homestead Property', lat: 25.4687, lon: -80.4776, value: 750000 }, { id: 3, name: 'Fort Lauderdale Property', lat: 26.1224, lon: -80.1373, value: 1200000 }, { id: 4, name: 'Naples Property', lat: 26.1420, lon: -81.7948, value: 1500000 } ]; document.getElementById('portfolio-upload').addEventListener('change', handleFileUpload); document.getElementById('portfolio-select').addEventListener('change', handlePortfolioSelect); async function handlePortfolioSelect(event) { const selectedPortfolioUrl = event.target.value; const portfolioStatus = document.getElementById('portfolio-status'); const portfolioUpload = document.getElementById('portfolio-upload'); if (!selectedPortfolioUrl) { customPortfolioData = null; portfolioStatus.textContent = ''; portfolioUpload.value = ''; // Clear file input return; } try { const response = await fetch(selectedPortfolioUrl); if (!response.ok) { throw new Error(`Failed to load portfolio: ${response.statusText}`); } const contents = await response.text(); const lines = contents.split('\n').filter(line => line.trim() !== ''); const headers = lines.shift().trim().split(',').map(h => h.toLowerCase()); const latIndex = headers.indexOf('lat'); const lonIndex = headers.indexOf('lon'); const tivIndex = headers.indexOf('tiv'); const deductibleIndex = headers.indexOf('deductible'); const limitIndex = headers.indexOf('limit'); if (latIndex === -1 || lonIndex === -1 || tivIndex === -1) { throw new Error('CSV must contain "lat", "lon", and "tiv" headers.'); } const parsedData = lines.map((line, index) => { const values = line.split(','); if (values.length <= Math.max(latIndex, lonIndex, tivIndex)) { console.error(`Skipping malformed CSV line ${index + 2}: ${line}`); return null; } const lat = parseFloat(values[latIndex]); const lon = parseFloat(values[lonIndex]); const tiv = parseFloat(values[tivIndex]); if (isNaN(lat) || isNaN(lon) || isNaN(tiv)) { console.error(`Skipping line with invalid number ${index + 2}: ${line}`); return null; } const deductible = deductibleIndex > -1 ? parseFloat(values[deductibleIndex]) || 0 : 0; const limit = limitIndex > -1 ? parseFloat(values[limitIndex]) || Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY; return { id: `custom-${index + 1}`, name: `Property ${index + 1}`, lat: lat, lon: lon, value: tiv, deductible: deductible, limit: limit }; }).filter(p => p !== null); customPortfolioData = parsedData; portfolioStatus.textContent = `${parsedData.length} properties loaded from sample portfolio.`; portfolioUpload.value = ''; // Clear file input as we are using a sample console.log('Sample portfolio loaded:', customPortfolioData); } catch (error) { console.error('Error loading sample portfolio:', error); portfolioStatus.textContent = `Error: ${error.message}`; customPortfolioData = null; } } function handleFileUpload(event) { const file = event.target.files[0]; if (!file) { return; } // Clear the sample portfolio dropdown document.getElementById('portfolio-select').value = ''; const reader = new FileReader(); reader.onload = function(e) { const contents = e.target.result; try { const lines = contents.split('\n').filter(line => line.trim() !== ''); const headers = lines.shift().trim().split(',').map(h => h.toLowerCase()); const latIndex = headers.indexOf('lat'); const lonIndex = headers.indexOf('lon'); const tivIndex = headers.indexOf('tiv'); const deductibleIndex = headers.indexOf('deductible'); const limitIndex = headers.indexOf('limit'); if (latIndex === -1 || lonIndex === -1 || tivIndex === -1) { throw new Error('CSV must contain "lat", "lon", and "tiv" headers.'); } const parsedData = lines.map((line, index) => { const values = line.split(','); if (values.length <= Math.max(latIndex, lonIndex, tivIndex)) { console.error(`Skipping malformed CSV line ${index + 2}: ${line}`); return null; } const lat = parseFloat(values[latIndex]); const lon = parseFloat(values[lonIndex]); const tiv = parseFloat(values[tivIndex]); if (isNaN(lat) || isNaN(lon) || isNaN(tiv)) { console.error(`Skipping line with invalid number ${index + 2}: ${line}`); return null; } const deductible = deductibleIndex > -1 ? parseFloat(values[deductibleIndex]) || 0 : 0; const limit = limitIndex > -1 ? parseFloat(values[limitIndex]) || Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY; return { id: `custom-${index + 1}`, name: `Property ${index + 1}`, lat: lat, lon: lon, value: tiv, deductible: deductible, limit: limit }; }).filter(p => p !== null); customPortfolioData = parsedData; document.getElementById('portfolio-status').textContent = `${parsedData.length} properties loaded.`; console.log('Custom portfolio loaded:', customPortfolioData); } catch (error) { console.error('Error parsing CSV:', error); document.getElementById('portfolio-status').textContent = `Error: ${error.message}`; customPortfolioData = null; } }; reader.readAsText(file); } async function loadHurricaneSelector() { try { const response = await fetch('api/hurricanes.php'); const hurricanes = await response.json(); const selectElement = document.getElementById('hurricane-select'); selectElement.innerHTML = ''; // Clear "Loading..." hurricanes.forEach(h => { const option = document.createElement('option'); option.value = h.id; option.textContent = `${h.name} (${h.year})`; selectElement.appendChild(option); }); } catch (error) { console.error('Error loading hurricane list:', error); const selectElement = document.getElementById('hurricane-select'); selectElement.innerHTML = ''; } } function getEstimatedWindSpeed(propertyPosition, stormTrack, propertyName, eventId) { console.log(`[getEstimatedWindSpeed] Called for property: ${propertyName}, event: ${eventId}`); let minDistance = Number.MAX_VALUE; let closestSegmentIndex = -1; if (!stormTrack || stormTrack.length === 0) { console.error(`[getEstimatedWindSpeed] Received empty or invalid stormTrack for event ${eventId}`); return { windSpeed: 0, distanceKm: 0, closestTrackPoint: null }; } console.log(`[getEstimatedWindSpeed] propertyPosition:`, propertyPosition); console.log(`[getEstimatedWindSpeed] stormTrack length:`, stormTrack.length); for (let i = 0; i < stormTrack.length; i++) { const lon = stormTrack[i][0]; const lat = stormTrack[i][1]; if (isNaN(lon) || isNaN(lat)) { console.error(`[getEstimatedWindSpeed] Invalid coordinates in stormTrack for event ${eventId} at index ${i}:`, stormTrack[i]); continue; } try { const trackPointPosition = Cesium.Cartesian3.fromDegrees(lon, lat); const distance = Cesium.Cartesian3.distance(propertyPosition, trackPointPosition); if (i < 5) { // Log first 5 distances console.log(`[getEstimatedWindSpeed] Index ${i}: lon=${lon}, lat=${lat}, distance=${distance}`); } if (distance < minDistance) { minDistance = distance; closestSegmentIndex = i; } } catch (e) { console.error(`[getEstimatedWindSpeed] Error processing track point ${i} for event ${eventId}: lon=${lon}, lat=${lat}`, e); } } if (closestSegmentIndex === -1) { console.error(`[getEstimatedWindSpeed] Could not find closest track point for event ${eventId}`); return { windSpeed: 0, distanceKm: 0, closestTrackPoint: null }; } console.log(`[getEstimatedWindSpeed] Closest segment index: ${closestSegmentIndex}`); const distanceKm = minDistance / 1000; const trackPoint = stormTrack[closestSegmentIndex]; const windOnTrackMph = trackPoint[2]; console.log(`[getEstimatedWindSpeed] minDistance (m): ${minDistance}`); console.log(`[getEstimatedWindSpeed] distanceKm: ${distanceKm}`); console.log(`[getEstimatedWindSpeed] closest trackPoint:`, trackPoint); console.log(`[getEstimatedWindSpeed] windOnTrackMph: ${windOnTrackMph}`); const decayConstant = 0.01386; // -ln(0.5) / 50km const estimatedWindMph = windOnTrackMph * Math.exp(-decayConstant * distanceKm); console.log(`[getEstimatedWindSpeed] decayConstant: ${decayConstant}`); console.log(`[getEstimatedWindSpeed] Math.exp term: ${Math.exp(-decayConstant * distanceKm)}`); console.log(`[getEstimatedWindSpeed] Final estimatedWindMph: ${estimatedWindMph}`); if (isNaN(estimatedWindMph)) { console.error(`[getEstimatedWindSpeed] FATAL: Resulting wind speed is NaN. Inputs: windOnTrack=${windOnTrackMph}, distanceKm=${distanceKm}`); } return { windSpeed: estimatedWindMph, distanceKm: distanceKm, closestTrackPoint: { lon: trackPoint[0], lat: trackPoint[1], wind: windOnTrackMph } }; } function calculateNetLoss(grossLoss, deductible, limit) { const lossAfterDeductible = Math.max(0, grossLoss - deductible); return Math.min(lossAfterDeductible, limit); } function getDamageRatio(windSpeedMph) { if (windSpeedMph < 74) return 0.0; if (windSpeedMph <= 95) return 0.03; if (windSpeedMph <= 110) return 0.08; if (windSpeedMph <= 129) return 0.15; if (windSpeedMph <= 156) return 0.30; return 0.50; } function renderEPCurve(epData) { const ctx = document.getElementById('epCurveChart').getContext('2d'); if (epCurveChart) { epCurveChart.destroy(); } const chartData = { datasets: [{ label: 'Exceedance Probability Curve', data: epData.map(p => ({ x: p.loss, y: p.exceedanceProbability })), borderColor: 'rgba(75, 192, 192, 1)', backgroundColor: 'rgba(75, 192, 192, 0.2)', borderWidth: 2, showLine: true, pointRadius: 4, pointBackgroundColor: 'rgba(75, 192, 192, 1)', }] }; epCurveChart = new Chart(ctx, { type: 'scatter', data: chartData, options: { responsive: true, maintainAspectRatio: false, title: { display: true, text: 'Exceedance Probability (EP) Curve' }, scales: { xAxes: [{ type: 'linear', position: 'bottom', scaleLabel: { display: true, labelString: 'Loss ($)' }, ticks: { callback: function(value) { return '$' + value.toLocaleString(); } } }], yAxes: [{ type: 'linear', scaleLabel: { display: true, labelString: 'Exceedance Probability' }, ticks: { min: 0, max: 1, callback: function(value) { return (value * 100).toFixed(0) + '%'; } } }] }, tooltips: { callbacks: { label: function(tooltipItem, data) { const datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; const loss = tooltipItem.xLabel.toLocaleString(); const prob = (tooltipItem.yLabel * 100).toFixed(2); return `${datasetLabel} Loss: $${loss} (EP: ${prob}%)`; } } } } }); } const runProbabilisticAnalysisButton = document.getElementById('runProbabilisticAnalysis'); runProbabilisticAnalysisButton.addEventListener('click', runProbabilisticAnalysis); async function runProbabilisticAnalysis() { console.log("Starting probabilistic analysis..."); catSimulationDataSource.entities.removeAll(); document.getElementById('cat-simulation-results').style.display = 'none'; const resultsDiv = document.getElementById('probabilistic-results'); resultsDiv.style.display = 'none'; const activePortfolio = customPortfolioData || portfolio; if (!activePortfolio || activePortfolio.length === 0) { alert("Please load a portfolio to run the analysis."); return; } try { const response = await fetch('api/stochastic_events.php'); if (!response.ok) { throw new Error(`Failed to fetch stochastic events: ${response.statusText}`); } const stochasticEvents = await response.json(); console.log(`Fetched ${stochasticEvents.length} stochastic events.`); console.log('Full stochastic events data:', stochasticEvents); console.log('Stochastic Events:', JSON.stringify(stochasticEvents, null, 2)); let averageAnnualLoss = 0; const eventLosses = []; stochasticEvents.forEach(event => { console.log('Processing event:', event); let eventTotalLoss = 0; activePortfolio.forEach(property => { try { console.log(`Processing property: ${property.name}, lon: ${property.lon}, lat: ${property.lat}`); if (isNaN(property.lon) || isNaN(property.lat)) { console.error('Invalid coordinates for property:', property); return; } const propertyPosition = Cesium.Cartesian3.fromDegrees(property.lon, property.lat); if (!event.track_data || !Array.isArray(event.track_data)) { console.error('Invalid track_data for event:', event); return; } const windData = getEstimatedWindSpeed(propertyPosition, event.track_data, property.name, event.event_id); if (!windData || windData.closestTrackPoint === null || isNaN(windData.windSpeed)) { console.error('Could not calculate valid wind data for property, skipping.', {property, event}); return; } const damageRatio = getDamageRatio(windData.windSpeed); const grossLoss = property.value * damageRatio; const netLoss = calculateNetLoss(grossLoss, property.deductible || 0, property.limit || Number.POSITIVE_INFINITY); eventTotalLoss += netLoss; } catch (e) { console.error(`An error occurred while processing property ${property.name} for event ${event.id}:`, e); } }); averageAnnualLoss += eventTotalLoss * event.probability; eventLosses.push({ loss: eventTotalLoss, probability: event.probability }); }); console.log(`Calculated Average Annual Loss (AAL): ${averageAnnualLoss}`); eventLosses.sort((a, b) => b.loss - a.loss); let cumulativeProbability = 0; const epData = eventLosses.map(event => { cumulativeProbability += event.probability; return { loss: event.loss, exceedanceProbability: cumulativeProbability }; }); epData.push({ loss: 0, exceedanceProbability: 1.0 }); console.log("EP Curve Data:", epData); document.getElementById('aal-value').textContent = `$${Math.round(averageAnnualLoss).toLocaleString()}`; resultsDiv.style.display = 'block'; renderEPCurve(epData); activePortfolio.forEach(property => { if (isNaN(property.lon) || isNaN(property.lat)) { return; } catSimulationDataSource.entities.add({ id: `property-${property.id}`, position: Cesium.Cartesian3.fromDegrees(property.lon, property.lat), point: { pixelSize: 10, color: Cesium.Color.BLUE, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: property.name, font: '12pt monospace', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -9) } }); }); viewer.flyTo(catSimulationDataSource.entities); } catch (error) { console.error("Error during probabilistic analysis:", error); alert(`An error occurred during analysis: ${error.message}`); } } const runCatSimulationButton = document.getElementById('runCatSimulation'); runCatSimulationButton.addEventListener('click', runCatSimulation); async function runCatSimulation() { catSimulationDataSource.entities.removeAll(); document.getElementById('cat-simulation-results').style.display = 'none'; let totalLoss = 0; const hurricaneSelect = document.getElementById('hurricane-select'); const selectedHurricaneId = hurricaneSelect.value; if (!selectedHurricaneId) { alert('Please select a hurricane to run the simulation.'); return; } const activePortfolio = customPortfolioData || portfolio; activePortfolio.forEach(property => { catSimulationDataSource.entities.add({ id: `property-${property.id}`, position: Cesium.Cartesian3.fromDegrees(property.lon, property.lat), point: { pixelSize: 10, color: Cesium.Color.GREEN, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, label: { text: property.name, font: '12pt monospace', style: Cesium.LabelStyle.FILL_AND_OUTLINE, outlineWidth: 2, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -9) } }); }); const response = await fetch(`api/hurricanes.php?id=${selectedHurricaneId}`); const hurricaneData = await response.json(); const trackPositions = hurricaneData.track.flatMap(p => [p[0], p[1]]); catSimulationDataSource.entities.add({ name: hurricaneData.name, polyline: { positions: Cesium.Cartesian3.fromDegreesArray(trackPositions), width: 3, material: Cesium.Color.RED.withAlpha(0.8) } }); activePortfolio.forEach(property => { const propertyPosition = Cesium.Cartesian3.fromDegrees(property.lon, property.lat); const windData = getEstimatedWindSpeed(propertyPosition, hurricaneData.track, property.name, hurricaneData.id); const damageRatio = getDamageRatio(windData.windSpeed); const grossLoss = property.value * damageRatio; const netLoss = calculateNetLoss(grossLoss, property.deductible || 0, property.limit || Number.POSITIVE_INFINITY); totalLoss += netLoss; const entity = catSimulationDataSource.entities.getById(`property-${property.id}`); let pointColor = Cesium.Color.GREEN; if (damageRatio > 0) { if (damageRatio <= 0.03) pointColor = Cesium.Color.YELLOW; else if (damageRatio <= 0.08) pointColor = Cesium.Color.ORANGE; else if (damageRatio <= 0.15) pointColor = Cesium.Color.RED; else pointColor = Cesium.Color.PURPLE; } entity.point.color = pointColor; entity.label.text = `${property.name}\nWind: ${windData.windSpeed.toFixed(0)} mph\nLoss: $${Math.round(netLoss).toLocaleString()}`; }); document.getElementById('total-loss-value').textContent = `$${Math.round(totalLoss).toLocaleString()}`; document.getElementById('cat-simulation-results').style.display = 'block'; viewer.flyTo(catSimulationDataSource.entities, { duration: 2.0 }); } } 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.
'; } } function waitForCesium() { if (typeof Cesium !== 'undefined') { initializeGlobe(); } else { console.log('Waiting for Cesium to load...'); setTimeout(waitForCesium, 100); } } document.addEventListener('DOMContentLoaded', waitForCesium);