Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Weather Dashboard</title> | |
| <!-- Bootstrap CSS for styling --> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <!-- Leaflet CSS for map --> | |
| <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> | |
| <style> | |
| body { | |
| background-color: #f4f6fa; | |
| font-family: Arial, sans-serif; | |
| } | |
| .header { | |
| text-align: center; | |
| padding: 20px; | |
| background-color: #007bff; | |
| color: white; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .metric-card { | |
| background-color: #ffffff; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| text-align: center; | |
| margin-bottom: 15px; | |
| } | |
| #map { | |
| height: 400px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .footer { | |
| font-size: 12px; | |
| text-align: center; | |
| margin-top: 30px; | |
| padding: 15px; | |
| background-color: #e9ecef; | |
| border-radius: 8px; | |
| } | |
| .city-section { | |
| margin-bottom: 20px; | |
| } | |
| .weather-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| .weather-table th, .weather-table td { | |
| border: 1px solid #dee2e6; | |
| padding: 8px; | |
| text-align: center; | |
| } | |
| .weather-table th { | |
| background-color: #f8f9fa; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <div class="header"> | |
| <h1>🌍 Global Weather Dashboard</h1> | |
| <p>Powered by Open-Meteo API</p> | |
| <img src="https://via.placeholder.com/100x50.png?text=Logo" alt="Logo" style="margin-top: 10px;"> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="container"> | |
| <!-- Input Form --> | |
| <div class="row mb-4"> | |
| <div class="col-md-6"> | |
| <label for="cityInput" class="form-label">Enter city names (comma-separated):</label> | |
| <input type="text" class="form-control" id="cityInput" placeholder="e.g., New York, London, Tokyo" value="New York, London, Tokyo"> | |
| </div> | |
| <div class="col-md-3"> | |
| <label for="citySelect" class="form-label">Or select a city:</label> | |
| <select class="form-select" id="citySelect"> | |
| <option value="">Select a city</option> | |
| <option value="New York">New York</option> | |
| <option value="London">London</option> | |
| <option value="Tokyo">Tokyo</option> | |
| <option value="Sydney">Sydney</option> | |
| <option value="Paris">Paris</option> | |
| <option value="Dubai">Dubai</option> | |
| <option value="Singapore">Singapore</option> | |
| </select> | |
| </div> | |
| <div class="col-md-3 align-self-end"> | |
| <button class="btn btn-primary w-100" onclick="fetchWeather()">Fetch Weather</button> | |
| </div> | |
| </div> | |
| <!-- Map --> | |
| <h3>City Locations</h3> | |
| <div id="map"></div> | |
| <!-- Weather Data --> | |
| <div id="weatherData"></div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="footer"> | |
| <p>Weather data by <a href="https://open-meteo.com" target="_blank">Open-Meteo.com</a> under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a> | | |
| For non-commercial use only | | |
| <a href="https://github.com/open-meteo/open-meteo" target="_blank">Source Code</a> | | |
| Contact: <a href="mailto:[email protected]">[email protected]</a></p> | |
| </div> | |
| <!-- Scripts --> | |
| <!-- Leaflet JS for map --> | |
| <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> | |
| <script> | |
| // Weather code to icon mapping | |
| const weatherIcons = { | |
| 0: "☀️", 1: "🌤️", 2: "⛅", 3: "☁️", 61: "🌧️", 71: "❄️" | |
| }; | |
| // Initialize Leaflet map | |
| const map = L.map('map').setView([0, 0], 2); | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' | |
| }).addTo(map); | |
| // Fetch weather data | |
| async function fetchWeather() { | |
| const cityInput = document.getElementById('cityInput').value; | |
| const citySelect = document.getElementById('citySelect').value; | |
| let cities = citySelect ? [citySelect] : cityInput.split(',').map(city => city.trim()).filter(city => city); | |
| cities = [...new Set(cities)]; // Remove duplicates | |
| if (cities.length === 0) { | |
| alert("Please enter or select at least one city."); | |
| return; | |
| } | |
| const weatherDataDiv = document.getElementById('weatherData'); | |
| weatherDataDiv.innerHTML = '<p>Loading weather data...</p>'; | |
| const coordinates = []; | |
| // Clear existing markers | |
| map.eachLayer(layer => { | |
| if (layer instanceof L.Marker) map.removeLayer(layer); | |
| }); | |
| for (const city of cities) { | |
| try { | |
| // Fetch coordinates | |
| const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json`; | |
| const geoResponse = await fetch(geoUrl); | |
| const geoData = await geoResponse.json(); | |
| if (geoData.results && geoData.results.length > 0) { | |
| const { latitude, longitude, country, timezone } = geoData.results[0]; | |
| coordinates.push({ city, lat: latitude, lon: longitude, country, timezone }); | |
| // Fetch weather data | |
| const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_mean,weathercode¤t_weather=true&temperature_unit=fahrenheit&timezone=auto`; | |
| const weatherResponse = await fetch(weatherUrl); | |
| const weatherData = await weatherResponse.json(); | |
| // Add marker to map | |
| L.marker([latitude, longitude]) | |
| .addTo(map) | |
| .bindPopup(`<b>${city}</b><br>Temp: ${weatherData.current_weather.temperature}°F<br>Country: ${country}`); | |
| // Render weather data | |
| const current = weatherData.current_weather; | |
| const daily = weatherData.daily; | |
| const localTime = new Date().toLocaleString('en-US', { timeZone: timezone }); | |
| let html = ` | |
| <div class="city-section"> | |
| <h4>🌆 Weather for ${city}</h4> | |
| <p>📍 ${city}, ${country} (Lat: ${latitude.toFixed(2)}, Lon: ${longitude.toFixed(2)})</p> | |
| <p>🕒 Local Time: ${localTime}</p> | |
| <h5>Current Weather</h5> | |
| <div class="row"> | |
| <div class="col-md-4"> | |
| <div class="metric-card"> | |
| <p><strong>Temperature</strong></p> | |
| <p>${current.temperature} °F</p> | |
| </div> | |
| </div> | |
| <div class="col-md-4"> | |
| <div class="metric-card"> | |
| <p><strong>Wind Speed</strong></p> | |
| <p>${current.windspeed} km/h</p> | |
| </div> | |
| </div> | |
| <div class="col-md-4"> | |
| <div class="metric-card"> | |
| <p><strong>Condition</strong></p> | |
| <p>${weatherIcons[current.weathercode] || '🌫️'} ${current.weathercode}</p> | |
| </div> | |
| </div> | |
| </div> | |
| <h5>7-Day Forecast</h5> | |
| <table class="weather-table"> | |
| <tr> | |
| <th>Date</th> | |
| <th>Max Temp (°F)</th> | |
| <th>Min Temp (°F)</th> | |
| <th>Precipitation Prob (%)</th> | |
| <th>Condition</th> | |
| </tr> | |
| `; | |
| for (let i = 0; i < daily.time.length; i++) { | |
| html += ` | |
| <tr> | |
| <td>${new Date(daily.time[i]).toLocaleDateString()}</td> | |
| <td>${daily.temperature_2m_max[i].toFixed(1)}</td> | |
| <td>${daily.temperature_2m_min[i].toFixed(1)}</td> | |
| <td>${(daily.precipitation_probability_mean[i] * 100).toFixed(0)}</td> | |
| <td>${weatherIcons[daily.weathercode[i]] || '🌫️'}</td> | |
| </tr> | |
| `; | |
| } | |
| html += `</table></div>`; | |
| weatherDataDiv.innerHTML += html; | |
| } else { | |
| weatherDataDiv.innerHTML += `<div class="alert alert-danger">Could not find coordinates for ${city}.</div>`; | |
| } | |
| } catch (error) { | |
| weatherDataDiv.innerHTML += `<div class="alert alert-danger">Error fetching data for ${city}: ${error.message}</div>`; | |
| } | |
| } | |
| // Adjust map to fit all markers | |
| if (coordinates.length > 0) { | |
| const bounds = L.latLngBounds(coordinates.map(c => [c.lat, c.lon])); | |
| map.fitBounds(bounds, { padding: [50, 50] }); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |