Pogoda dla OBS

Zaczynam drugie podejście do Rainbow Star Radio – tzw. radio z wizją. W ubiegłym roku, przez sześć miesięcy prowadziłem testy nadawania w technologii Nullsoft Shoutcast, ale – jakkolwiek udało się super – nie o to mi chodziło. Plan bowiem był taki, żeby transmitować na żywo na YouTube, zapełnić stream produkcją własną i licencjonowaną. Testy, choć wciąż na razie muzyczne dzieją się właśnie na kanale Rainbow Star, na YouTube.

Czymże by była transmisja takiego programu, gdyby nie było w niej… pogody? 😀 Szczęśliwie mam szerokie kompetencje, w streamingu jestem dobry, umiem sam bez grantów zrobić wiele rzeczy, więc skutek jest – i owszem.

Poniższy kod odpytuje serwis OpenWeather na okoliczność temperatury i warunków atmo w różnych miejscowościach. Koncept został opracowany po to, żeby umieścić go jako aktywowaną co jakiś czas scenę w OBS.

Enjoy!

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dane Pogodowe</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;500;700;800;900&display=swap');
body {
  display: flex;
  justify-content: center; /* Wyśrodkowanie w poziomie */
  align-items: center; /* Wyśrodkowanie w pionie */
  min-height: 100vh; /* Pełna wysokość widoku */
  margin: 0; /* Usunięcie domyślnego marginesu */
	background-color: #1c77c3;
	background-image: radial-gradient(circle, #F9A72B 0%, #FA9026 70%, #FB6C1F 100%);
}
  #weatherData {
    transition: opacity 1s ease-in-out;
    opacity: 0; /* zacznij jako niewidoczny */
	text-align:center;
	z-index: 100;
  }

  .fade-in {
    animation: fadeIn 1.5s forwards;
  }

  .fade-out {
    animation: fadeOut 1.5s forwards;
  }

  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  @keyframes fadeOut {
    from { opacity: 1; }
    to { opacity: 0; }
  }

h1 {font-family: 'Roboto Condensed'; font-weight: 700; font-size: 100px; color: #fff; text-transform:uppercase; margin-top: 0;}
h2 {font-family: 'Roboto Condensed'; font-weight: 400; font-size: 60px; color: #fff; margin-top:-80px;}


/* Styl głównego obrazu */
.mainicon {
  width: 300px;
  height: auto;
  filter: invert(100%);
}

/* Styl dodatkowych ikon */
.iconadd {
  padding: 10px;
  width: 100px; /* !important powinno być używane jako ostateczność */
  height: 100px; /* !important powinno być używane jako ostateczność */
  filter: invert(100%);
}

/* Styl tabeli prognozy */
.forecastimg {
  width: 100%;
  max-width: 900px;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 3px;
}

/* Styl wspólny dla czasu, prognozy i temperatury */
.czas, .prognoza, .temperatura {
  font-family: 'Roboto Condensed', sans-serif; /* Dodane sans-serif jako fallback */
  font-weight: 400;
  font-size: 24px;
  color: #000;
  text-align: center;
  padding: 10px;
  background: transparent;
  border: 1px solid #fff; /* Ustaw na 1px jak w poprzednim stylu, albo 2px jak w twoim ostatnim */
}

/* Styl specyficzny dla czasu */
.czas {
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  background: #fff;
}

/* Styl specyficzny dla temperatury */
.temperatura {
  border-bottom-right-radius: 6px;
  border-bottom-left-radius: 6px;
  background: #fff;
}


/* animacja tła */
#Clouds {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	margin: auto;
	height: 30%;
	overflow: hidden;
	animation: FadeIn 3.1s ease-out;
	user-select: none;
}

@keyframes FadeIn {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

.Cloud {
	position: absolute;
	width: 100%;
	background-repeat: no-repeat;
	background-size: auto 100%;
	height: 70px;
	animation-duration: 120s;
	animation-iteration-count: infinite;
	animation-fill-mode: forwards;
	animation-timing-function: linear;
	animation-name: Float, FadeFloat;
	z-index: 2;
}

.Cloud.Foreground {
	height: 10%;
	min-height: 20px;
	z-index: 4;
}

.Cloud.Background {
	height: 9.09090909%;
	min-height: 8px;
	animation-duration: 210s;
}

@keyframes Float {
	from {
		transform: translateX(100%) translateZ(0);
	}

	to {
		transform: translateX(-15%) translateZ(0);
	}
}
/*
@keyframes Float {
  from { transform: translateX(100%) translateY(-100%) translateZ(0); }
  50% { transform: translateX(55%) translateY(0) translateZ(0); }
  to { transform: translateX(-5%) translateY(-100%) translateZ(0); }
}
*/
@keyframes FadeFloat {
	0%,
  100% {
		opacity: 0;
	}

	5%,
  90% {
		opacity: 1;
	}
}

.Cloud:nth-child(10) {
	animation-delay: -184.61538462s;
	top: 60%;
}

.Cloud.Foreground:nth-child(10) {
	animation-duration: 80s;
	height: 35%;
}

.Cloud.Background:nth-child(10) {
	animation-duration: 110s;
	height: -3.40909091%;
}

.Cloud:nth-child(9) {
	animation-delay: -166.15384615s;
	top: 54%;
}

.Cloud.Foreground:nth-child(9) {
	animation-duration: 84s;
	height: 32.5%;
}

.Cloud.Background:nth-child(9) {
	animation-duration: 114s;
	height: -2.15909091%;
}

.Cloud:nth-child(8) {
	animation-delay: -147.69230769s;
	top: 48%;
}

.Cloud.Foreground:nth-child(8) {
	animation-duration: 88s;
	height: 30%;
}

.Cloud.Background:nth-child(8) {
	animation-duration: 118s;
	height: -0.90909091%;
}

.Cloud:nth-child(7) {
	animation-delay: -129.23076923s;
	top: 42%;
}

.Cloud.Foreground:nth-child(7) {
	animation-duration: 92s;
	height: 27.5%;
}

.Cloud.Background:nth-child(7) {
	animation-duration: 122s;
	height: 0.34090909%;
}

.Cloud:nth-child(6) {
	animation-delay: -110.76923077s;
	top: 36%;
}

.Cloud.Foreground:nth-child(6) {
	animation-duration: 96s;
	height: 25%;
}

.Cloud.Background:nth-child(6) {
	animation-duration: 126s;
	height: 1.59090909%;
}

.Cloud:nth-child(5) {
	animation-delay: -92.30769231s;
	top: 30%;
}

.Cloud.Foreground:nth-child(5) {
	animation-duration: 100s;
	height: 22.5%;
}

.Cloud.Background:nth-child(5) {
	animation-duration: 130s;
	height: 2.84090909%;
}

.Cloud:nth-child(4) {
	animation-delay: -73.84615385s;
	top: 24%;
}

.Cloud.Foreground:nth-child(4) {
	animation-duration: 104s;
	height: 20%;
}

.Cloud.Background:nth-child(4) {
	animation-duration: 134s;
	height: 4.09090909%;
}

.Cloud:nth-child(3) {
	animation-delay: -55.38461538s;
	top: 18%;
}

.Cloud.Foreground:nth-child(3) {
	animation-duration: 108s;
	height: 17.5%;
}

.Cloud.Background:nth-child(3) {
	animation-duration: 138s;
	height: 5.34090909%;
}

.Cloud:nth-child(2) {
	animation-delay: -36.92307692s;
	top: 12%;
}

.Cloud.Foreground:nth-child(2) {
	animation-duration: 112s;
	height: 15%;
}

.Cloud.Background:nth-child(2) {
	animation-duration: 142s;
	height: 6.59090909%;
}

.Cloud:nth-child(1) {
	animation-delay: -18.46153846s;
	top: 6%;
}

.Cloud.Foreground:nth-child(1) {
	animation-duration: 116s;
	height: 12.5%;
}

.Cloud.Background:nth-child(1) {
	animation-duration: 146s;
	height: 7.84090909%;
}

.Cloud {
	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKQAAABgCAYAAACTzNnjAAAFCklEQVR42u3d34uVRRjA8YMsEi0iSwhdRBGhSJgZiNRFIkWhQVEXBipKUVBBLCF6k0h4UxFkBLq4QT/Qiyi80EgxCjXMWqOMtqy0bBNja92yXatN3c3pGc9sHU/v+X3emWfe93vx+QN23u+e95x5Z94pmNWFQo5NEytErzgoBsSIOCf+FqbEBTEhzophcVTsFxvFEjE152PZHGMukbcB6BAPij0uqgtl0bXCBvyjeFPcS2wEWc0y0ec++Ywnf7rwbyc8grQ6RY8Y9RhhJT+Jde4TmghzFuR08bo4ryDEpE/N58QUYsx+kFPcD4yzCkMsZz+1uwkyu0HeKYYiCLHcN2I2QWYnSPudbHubfy37NuFu4wQZeZD2k2Uw4hDLHRZdBBnnH3K/5ykcX86IeQQZl6civ0XXYv/RlhJkHLZkOMTypz7dBKnbyzmJsfTZeTdB6vRszmIsjXI5QeryWMa/M9ZinzjdTJA63CLGcxzjpD/ElQQZfq3iKDFe8lSHIAPqI8L/6SHIMB4nvorTQQsI0v+teoz4KjpBkH7tIrqaNhCkH9cnbK5C8q/uywiSHzKabCLIdM3K+QR4o8YysfVWcZDvElnD1hNkOi5XuilLu5MlOyzni3vESnGfWCRuIMjm1zgSWPM7GWvNXdonXkdM8Q0dCwmytu8Iy/vK9B3iRoJMnghnqifc8jb7yXkXQf5nLWGo2WR2HUGuLrxPDKq25D6d9yBPE4I6/e6rVO6CnMpkuFq/eLmFKwtyMRde/dOg2XkKkvlH/X5P9ZNSWZBvcMGjMJza6iJlQe7nYkfjozwE+TkXOiprsx7kES5yVOzLYK/IcpA/cJGj806Wg/yaCxzlzsersxpkPxc4SruzGuQBLm603yU7DPOQUOSJWIK0E6j2lcvPm+JJVp+674oDbiFuv5t/fEXs5cJG65DmIO3KY3vG3yCLbXO1N1xVkB3uWfQQFye3rtUS5DN1bC5C9j0cOki7XOwUFwJOb6gg7TmCW1lMizI7QwQ5nW2qqGCv7yDtwsxfGXhU0OczyKvECIOOKvb5CnKa2+TDoKOat3wFyaIH1ONFH0H2MNCo00NpBzmXR39owIy0gzzOIKNOv6X9LHsZg4wGp3xsM/ZMoUfF3aaZ4++qBDnIIKMNzrsHKb11Lb6oEORtDCRSYB81f+vWxzYUJBv2kTYb5vx6gzzHgMHTjsUXagV5BwMFzz78d5NYQpDbGCAEcOzissaEIL9kcBDIx0lBsrwMIW0uD3KcQUHgqaEFpUGyLQGhnSgNkgGBBg8QJDQ5Phkky82g5bvkNQXDscDQY6MNcpiBgBL9NsjDDAS0LPq1Qb7EQECJicLFSUkGAkpMLgMaYzCgKci3GQxomPqZDPImBgMa9uOULifnDWcI7VRpkIsYEAR2sHwX2GcMCgJak/QKPh4lIsgcpOhM2iv7JIODAPqqvY7vPQYInlf6zKsWpN2iOMBAwZMD9bzSudPwvh+kr3gofJ0vve/ikxIp36pXNHosiD2j5gMGD2ksyG3lJK917qc5A4l2eNW04Wi5OaZ41DADilZu0xtMmw/fXCV+ZnDRoFFTfBdpaudlLxVfsXMRNYy7W3SH8XSAu/01bo8s/kL8xQWAM+JC7DIeDnCvZqZ4RGxxi3/3iUPOJ+5TtRXfG/v6jeadNMVjlqsZquG0uwU164ybg2vFuPuh2ax23tkm3N91VLwmbjVNvPT+H7Ro4730ITNPAAAAAElFTkSuQmCC);
}

.Cloud.Background {
	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAAAqCAYAAAAUJM0rAAACFUlEQVRo3u3aPSwDYRzH8UZEbAYiYrDYjLZGpIPBJLGwSKwi0k1iYBKDdFJsEiMxSZgsFZJGiGjqJWEhSKuaNKRUG8r5PfI8SXOud72+PH2eu2f47Ndv7+V5/nee/G6bh5MWmIV9iEMWvkGjfiADD7AHk9BU6+PSNK0kPAINwwXkC6KU6gvC4HVyKB/clhHHCDnbTqHbSaEaYIv+OK3KyBk274RQrXBXg0B6B/QPkTIUiZTiEIm5gkbZQjXTJ5nG2ZFsocJ1iMQEZQk1VcdIGl2L9Yoeitwj3uocirgRPVRAgEjMoMihkgKFStIzK063QVHYhKF6h/IKFMlKGpaNlhM8Qq1JFIp5gQHeoSIShmJPST/PUElJQ7FN9hivUGmJQxE56OQRKiN5qL/NNY9QKQeEIpdgR7VC9cEqHeGeUyEHXHrMUiWhyKx6BV4dEsNMtNxQI4Ls33h5LidUoEYjXKEXoXZDLbgsEBOzE8qne8fmJhE7oWIujUQcwhyMmy0VSKRRF0cyWlclYMYo1LEKZOgRugpDpVWUot5ZLI8LlwN23bNQKoY1vwpVmmsS6kOFsPRJQl2qENZIqGkVwnp9xb5nUksEcxm2hZlQMcy3OYXTgx0VpOi2pl8/jwqpMP9sF5twBl08ctE7I/dws5l5D5y4OBj51HvdzluYdlik0RL06cjkKpSlB1Span5x/AQb5Hfrxyy/oU5ISeVw53AAAAAASUVORK5CYII=);
}
</style>
</head>
<body>
<div id="Clouds">
  <div class="Cloud Foreground"></div>
  <div class="Cloud Background"></div>
  <div class="Cloud Foreground"></div>
  <div class="Cloud Background"></div>
  <div class="Cloud Foreground"></div>
  <div class="Cloud Background"></div>
  <div class="Cloud Background"></div>
  <div class="Cloud Foreground"></div>
  <div class="Cloud Background"></div>
  <div class="Cloud Background"></div>
</div>

<svg version="1.1" id="chmurki" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="40px" height="24px" viewBox="0 0 40 24" enable- xml:space="preserve">
  <defs>
    <path id="Cloud" d="M33.85,14.388c-0.176,0-0.343,0.034-0.513,0.054c0.184-0.587,0.279-1.208,0.279-1.853c0-3.463-2.809-6.271-6.272-6.271
	c-0.38,0-0.752,0.039-1.113,0.104C24.874,2.677,21.293,0,17.083,0c-5.379,0-9.739,4.361-9.739,9.738
	c0,0.418,0.035,0.826,0.084,1.229c-0.375-0.069-0.761-0.11-1.155-0.11C2.811,10.856,0,13.665,0,17.126
	c0,3.467,2.811,6.275,6.272,6.275c0.214,0,27.156,0.109,27.577,0.109c2.519,0,4.56-2.043,4.56-4.562
	C38.409,16.43,36.368,14.388,33.85,14.388z"/>
  </defs>
</svg>
<div id="weatherData"></div>
<!--<pre id="rawData"></pre>-->
<script>
document.addEventListener('DOMContentLoaded', function () {

let przedzial_godzinowy = 6; // Ilość kolejnych godzin do wyświetlenia
let pokaz_od_godziny = 'current'; // Pokaż od konkretnej godziny (np. 22:00) lub wpisz 'current'

const citiesData=[
{name:"Białystok",latitude:53.1333,longitude:23.1643},
{name:"Bydgoszcz",latitude:53.1235,longitude:18.0076},
{name:"Gdańsk",latitude:54.3523,longitude:18.6491},
{name:"Gdynia",latitude:54.4981,longitude:18.498},
{name:"Gorzów",latitude:52.7333,longitude:15.25},
{name:"Katowice",latitude:50.2584,longitude:19.0275},
{name:"Kielce",latitude:50.8703,longitude:20.6275},
{name:"Kraków",latitude:50.0614,longitude:19.9366},
{name:"Lublin",latitude:51.25,longitude:22.5667},
{name:"Łódź",latitude:51.7706,longitude:19.4739},
{name:"Olsztyn",latitude:53.7799,longitude:20.4942},
{name:"Opole",latitude:50.6721,longitude:17.9253},
{name:"Poznań",latitude:52.4069,longitude:16.9299},
{name:"Rzeszów",latitude:50.0413,longitude:21.999},
{name:"Szczecin",latitude:53.4289,longitude:14.553},
{name:"Toruń",latitude:53.0138,longitude:18.5981},
{name:"Warszawa",latitude:52.2298,longitude:21.0118},
{name:"Wrocław",latitude:51.1,longitude:17.0333},
{name:"Zielona Góra",latitude:51.9355,longitude:15.5064}
];

const grafika=[
{code:0,day:"d0.svg",night:"n0.svg"},
{code:1,day:"d1.svg",night:"n1.svg"},
{code:2,day:"d2.svg",night:"n2.svg"},
{code:3,day:"d3.svg",night:"n3.svg"},
{code:45,day:"d45.svg",night:"n45.svg"},
{code:48,day:"d48.svg",night:"n48.svg"},
{code:51,day:"d51.svg",night:"n51.svg"},
{code:53,day:"d53.svg",night:"n53.svg"},
{code:55,day:"d55.svg",night:"n55.svg"},
{code:56,day:"d56.svg",night:"n56.svg"},
{code:57,day:"d57.svg",night:"n57.svg"},
{code:61,day:"d61.svg",night:"n61.svg"},
{code:63,day:"d63.svg",night:"n63.svg"},
{code:65,day:"d65.svg",night:"n65.svg"},
{code:66,day:"d66.svg",night:"n66.svg"},
{code:67,day:"d67.svg",night:"n67.svg"},
{code:71,day:"d71.svg",night:"n71.svg"},
{code:73,day:"d73.svg",night:"n73.svg"},
{code:75,day:"d75.svg",night:"n75.svg"},
{code:77,day:"d77.svg",night:"n77.svg"},
{code:80,day:"d80.svg",night:"n80.svg"},
{code:81,day:"d81.svg",night:"n81.svg"},
{code:82,day:"d82.svg",night:"n82.svg"},
{code:85,day:"d85.svg",night:"n85.svg"},
{code:86,day:"d86.svg",night:"n86.svg"},
{code:95,day:"d95.svg",night:"n95.svg"},
{code:96,day:"d96.svg",night:"n96.svg"},
{code:99,day:"d99.svg",night:"n99.svg"}
];

function showWeatherData() {
    const weatherDiv = document.getElementById('weatherData');
    weatherDiv.style.opacity = 1; // Rozpoczyna się od pełnej widoczności
    weatherDiv.classList.add('fade-in');
  }

  function hideWeatherData(callback) {
    const weatherDiv = document.getElementById('weatherData');
    weatherDiv.classList.replace('fade-in', 'fade-out');
    
    // Po zakończeniu animacji fade-out, wywołuje podaną funkcję zwrotną
    weatherDiv.addEventListener('animationend', function handleAnimationEnd() {
      weatherDiv.style.opacity = 0; // Ustawia na nieprzezroczyste
      weatherDiv.classList.remove('fade-out');
      weatherDiv.removeEventListener('animationend', handleAnimationEnd);
      
      if (callback) callback();
    });
  }


  let currentCityIndex = 0;

  function fetchWeatherData() {
    if (currentCityIndex >= citiesData.length) {
      console.log("Wszystkie miasta zostały przetworzone.");
      return; // Stop if all cities have been processed
    }

// Przygotowujemy ciągi znaków zawierające odpowiednio szerokości i długości geograficzne
const latitudes = citiesData.map(city => city.latitude).join(',');
const longitudes = citiesData.map(city => city.longitude).join(',');
    const city = citiesData[currentCityIndex];
    const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${city.latitude}&longitude=${city.longitude}&current=temperature_2m,is_day,weather_code&hourly=temperature_2m,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&timezone=Europe%2FBerlin&forecast_days=2`;



    fetch(apiUrl)
      .then(response => response.json())
      .then(data => {
            const hourlyData = data.hourly;
            const currentTime = new Date(data.current.time);
            const closestHourIndex = hourlyData.time.findIndex(time => new Date(time) > currentTime) - 1;
            const currentTemperature = hourlyData.temperature_2m[closestHourIndex];

            let startIndex = pokaz_od_godziny === 'current' ?
                closestHourIndex + 1 :
                hourlyData.time.findIndex(time => time.endsWith(pokaz_od_godziny));
            startIndex = startIndex === -1 ? 0 : startIndex;
            const endIndex = startIndex + przedzial_godzinowy;

            const dailyData = data.daily;

            // Funkcja do określenia, czy jest dzień czy noc
            const isDayTime = (time, dailyData) => {
                const timeDate = new Date(time);
                for (let i = 0; i < dailyData.time.length; i++) {
                    const sunriseDate = new Date(dailyData.sunrise[i]);
                    const sunsetDate = new Date(dailyData.sunset[i]);
                    if (timeDate >= sunriseDate && timeDate < sunsetDate) {
                        return 'd';
                    }
                }
                return 'n';
            };

            const hourlyForecast = hourlyData.time.slice(startIndex, endIndex).map((time, index) => {
                const dayStatus = isDayTime(time, dailyData);
                return {
                    time,
                    temperature: hourlyData.temperature_2m[startIndex + index],
                    weather_code: hourlyData.weather_code[startIndex + index],
                    day_status: dayStatus // Dodanie statusu dnia/nocy
                };
            });
			const currentWeatherCode = hourlyData.weather_code[closestHourIndex];
			const dayOrNight = isDayTime(hourlyData.time[closestHourIndex], dailyData);
            // Wyświetlanie danych na stronie
        document.getElementById('weatherData').innerHTML = `
		<img class="mainicon" src="img/pogoda/${currentWeatherCode}${dayOrNight}.svg" alt="">
          <h1>${city.name}</h1>
    <h2>${currentTemperature}°C</h2>
<table class="forecastimg">
  <tr>
    ${hourlyForecast.map(entry => `<td class="czas">${entry.time.slice(-5)}</td>`).join('')}
  </tr>
  <tr>
    ${hourlyForecast.map(entry => `<td class="prognoza"><img class="iconadd" src="img/pogoda/${entry.weather_code}${entry.day_status}.svg" alt=""></td>`).join('')}
  </tr>
  <tr>
    ${hourlyForecast.map(entry => `<td class="temperatura">${entry.temperature}°C</td>`).join('')}
  </tr>
</table>
`;

showWeatherData();
        setTimeout(() => {
          hideWeatherData(() => {
            if (++currentCityIndex < citiesData.length) {
              fetchWeatherData(); // Pobierz dane dla następnego miasta
            }
          });
        }, 5000);
      })
      .catch(error => {
        console.error('Błąd podczas pobierania danych pogodowych:', error);
        document.getElementById('weatherData').innerHTML = '<p>Błąd podczas ładowania danych.</p>';
        showWeatherData();
        setTimeout(() => {
          hideWeatherData(() => {
            if (++currentCityIndex < citiesData.length) {
              fetchWeatherData(); // Pobierz dane dla następnego miasta
            }
          });
        }, 3000);
      });
  }

  fetchWeatherData();
});
</script>
</body>
</html>