Files
weather-webapp/templates/index.html
2025-07-15 10:18:13 +02:00

317 lines
8.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8" />
<title>Pogoda Web</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #e0f7fa, #e8eaf6);
color: #333;
}
header {
padding: 20px;
background: #2196f3;
color: white;
text-align: center;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
main {
max-width: 1000px;
margin: auto;
padding: 20px;
}
#controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
input, button {
font-size: 1em;
padding: 10px;
border-radius: 6px;
border: 1px solid #ccc;
}
button {
background: #2196f3;
color: white;
border: none;
transition: background 0.3s;
cursor: pointer;
}
button:hover {
background: #1976d2;
}
#map {
height: 400px;
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
#result {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
white-space: pre-wrap;
margin-bottom: 20px;
}
canvas {
background: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
margin-bottom: 40px;
}
</style>
</head>
<body>
<header>
<h1>🌦️ Pogoda Web</h1>
<p>Kliknij na mapie lub wyszukaj miasto, aby zobaczyć prognozę pogody</p>
</header>
<main>
<div id="controls">
<input type="text" id="cityInput" placeholder="Wpisz miasto" />
<button onclick="searchCity()">Szukaj</button>
</div>
<div id="map"></div>
<div id="result">Kliknij na mapie lub wyszukaj miasto, aby zobaczyć pogodę.</div>
<canvas id="dailyChart"></canvas>
<canvas id="hourlyChart"></canvas>
</main>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const map = L.map('map').setView([52.237, 21.017], 6);
let marker = null;
let currentPlaceName = '';
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(map);
async function fetchPlaceName(lat, lon) {
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}`;
const response = await fetch(url, { headers: { 'User-Agent': 'PogodaApp/1.0' } });
if (!response.ok) throw new Error('Błąd podczas pobierania nazwy miejsca.');
const data = await response.json();
return data.display_name || 'Nieznana lokalizacja';
}
function setMarker(lat, lon, name) {
if (marker) map.removeLayer(marker);
marker = L.marker([lat, lon]).addTo(map);
if (name) marker.bindPopup(name).openPopup();
}
map.on('click', async (e) => {
const { lat, lng } = e.latlng;
setMarker(lat, lng, "Ładowanie nazwy...");
updateResult("Ładowanie nazwy lokalizacji...");
try {
const placeName = await fetchPlaceName(lat, lng);
currentPlaceName = placeName;
setMarker(lat, lng, placeName);
updateResult("Ładowanie pogody...");
await fetchWeather(lat, lng);
} catch (err) {
updateResult("Błąd: " + err.message);
}
});
function updateResult(text) {
document.getElementById('result').innerText = text;
}
async function searchCity() {
const city = document.getElementById('cityInput').value.trim();
if (!city) {
updateResult("Proszę wpisać nazwę miasta.");
return;
}
updateResult("Wyszukiwanie miasta...");
try {
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(city)}`);
if (!res.ok) throw new Error('Błąd podczas wyszukiwania miasta.');
const data = await res.json();
if (data.length === 0) {
updateResult("Nie znaleziono miasta.");
return;
}
const { lat, lon, display_name } = data[0];
currentPlaceName = display_name;
map.setView([lat, lon], 10);
setMarker(lat, lon, display_name);
updateResult("Ładowanie pogody...");
await fetchWeather(lat, lon);
} catch (err) {
updateResult("Błąd: " + err.message);
}
}
let dailyChart = null;
let hourlyChart = null;
async function fetchWeather(lat, lon) {
try {
const res = await fetch('/get_weather', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lat, lon })
});
if (!res.ok) throw new Error(`Błąd serwera: ${res.status}`);
const data = await res.json();
if (data.error) {
updateResult("Błąd serwera: " + data.error);
return;
}
const current = data.current || {};
const daily = data.daily?.forecast || {};
const hourly = data.hourly?.hourly_forecast || {};
let resultText = `Lokalizacja: ${currentPlaceName}
Temperatura: ${current.temperature ?? 'brak danych'} °C
Wiatr: ${current.windspeed ?? 'brak danych'} km/h
Czas pomiaru: ${current.time ? new Date(current.time).toLocaleString() : 'brak danych'}`;
if (daily.time?.length) {
resultText += "\n\nPrognoza 7-dniowa:\n";
for (let i = 0; i < daily.time.length; i++) {
resultText += `${daily.time[i]}: ${daily.temperature_2m_min[i]}${daily.temperature_2m_max[i]} °C, Opady: ${daily.precipitation_sum[i]} mm\n`;
}
}
if (hourly.time?.length) {
resultText += "\n\nNajbliższe godziny:\n";
for (let i = 0; i < Math.min(24, hourly.time.length); i++) {
resultText += `${hourly.time[i]}: ${hourly.temperature_2m[i]} °C, Wiatr: ${hourly.windspeed_10m[i]} km/h, Opady: ${hourly.precipitation[i]} mm\n`;
}
}
updateResult(resultText.trim());
drawDailyChart(daily);
drawHourlyChart(hourly);
} catch (err) {
updateResult("Błąd podczas pobierania pogody: " + err.message);
}
}
function drawDailyChart(data) {
const ctx = document.getElementById('dailyChart').getContext('2d');
if (dailyChart) dailyChart.destroy();
dailyChart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.time,
datasets: [
{
label: 'Temp. max (°C)',
data: data.temperature_2m_max,
backgroundColor: 'rgba(255, 99, 132, 0.6)'
},
{
label: 'Temp. min (°C)',
data: data.temperature_2m_min,
backgroundColor: 'rgba(54, 162, 235, 0.6)'
},
{
label: 'Opady (mm)',
data: data.precipitation_sum,
type: 'line',
borderColor: 'rgba(0, 128, 0, 0.8)',
fill: false,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
stacked: false,
scales: {
y: {
type: 'linear',
position: 'left',
title: { display: true, text: 'Temperatura (°C)' }
},
y1: {
type: 'linear',
position: 'right',
title: { display: true, text: 'Opady (mm)' },
grid: { drawOnChartArea: false }
}
}
}
});
}
function drawHourlyChart(data) {
const ctx = document.getElementById('hourlyChart').getContext('2d');
if (hourlyChart) hourlyChart.destroy();
hourlyChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.time,
datasets: [
{
label: 'Temperatura (°C)',
data: data.temperature_2m,
borderColor: 'orange',
fill: false
},
{
label: 'Wiatr (km/h)',
data: data.windspeed_10m,
borderColor: 'blue',
fill: false
},
{
label: 'Opady (mm)',
data: data.precipitation,
borderColor: 'green',
fill: false
}
]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
title: { display: true, text: 'Wartość' }
}
}
}
});
}
</script>
</body>
</html>