Удобный прогноз северного сияния: в цветах и графиках
В начале 2022 года я съездил в самостоятельное путешествие за северным сиянием. Это оказалось прекрасно, кроме этапа планирования. Все сайты с прогнозами «северных огней» выглядели странно и едва помогали собраться, но зато предлагали купить в пару кликов тур и ни о чем не переживать. Тур мне был не нужен, а вот хороший прогноз — да.
В конце лета я решил, что хочу написать свой небольшой опенсорс сайт, где будет просто и удобно узнать прогноз сияния самым обычным туристам вроде меня. Рассказываю, что из этого получилось.
Пестрые краски и занятные пустоты
Общая идея всех проектов с прогнозами сияния мне нравилась: это несколько цветных графиков, иногда советы, подсказки и объясняющие тексты где-то рядом. Но в тоже время реализация казалась странной, особенно две общие черты между почти любыми ресурсами: резкие, яркие цвета и внезапный английский язык.
Ничего не имею против ни того, ни другого. Но смотреть ночью в машине посреди заполярья на красный цвет столбцов — не самое приятное занятие для глаз. А ещё мне казалось совершенно непонятным, почему на русскоязычных сайтах подписи для графиков на английском, ведь вся остальная информация на русском. Позже начал догадываться, что это связано с входными данными, переводом которым никто не обеспокоился.
Когда я только собирался в путешествие, ничего о КП-индексах мне известно не было, но в тоже время большинство сайтов сразу демонстрировали графики с непонятными цифрами и любопытными цветами: жёлтый это плохое сияние или сильное? А красный? В тоже время полезные тексты, вроде «что такое КП-индекс», лежали либо в футере, либо вовсе на отдельных страницах, до которых предстояло добраться через затейливую навигацию.
Последнее место для модернизации обнаружилось прямо в моём проекте. В очередной день написания и проверки кода я заметил, что графики опустели. Более того, выяснилось, что рухнули вообще все сайты с прогнозами сияния, по крайней мере в русскоязычном сегменте. Оперативный дебаг показал, что вид записи входных данных немного изменился, поэтому из-за ненадежного кода у всех упали графики. До сих пор некоторые сайты не оправились от того «удара».
Итак, я решил учесть эти и некоторые другие проблемы, чтобы сделать свой сайт лучше и удобнее, а заодно утолить любопытство и выяснить, откуда все-таки берутся эти прогнозы. Вот какой вид приняло бы условное ТЗ:
Три коротких секции на сайт. Первая введет человека в курс дела, объяснит, как разобраться с информацией на странице и куда вообще он попал. Вторая покажет графики с индексами. Третья предложит элементарный тест для тех, кто волнуется и хочет проверить свою готовность к путешествию. Перед своей поездкой я бы хотел пройти такую самопроверку;
Цветовая лаконичность и логичность: в итоге красный график круче желтого или как?
Подписи для графиков на русском языке;
Надежный код, который просто так не сломается от мелких новшеств во входных данных;
Простота во всём: зашёл, понял что за КП-индексы, посмотрел графики, проверил себя, ушёл.
После обмозгования мной структуры пришло время писать код и запускать его для людей, ведь северное сияния видно уже с осени, а дело близилось к зиме.
КП-индексы — кто вы и откуда
Чтобы подгадать даты поездки, в самом любительском варианте нужно смотреть на КП-индексы, — они показывают планетарный уровень геомагнитной активности и обозначаются цифрами от 0 до 9. Хотя опытные туристы наблюдают за иными параметрами, КП-индексы лежат в основе почти всех сайтов с прогнозами, да и корреляция с яркостью, цветностью сияния замечается: чем выше индекс, тем интенсивнее солнечная активность и, как следствие, выше вероятность увидеть что-то захватывающее.
Радовало, что от сайта к сайту отображались одинаковые данные, а это явно намекало на их единый источник. Тем не менее, поиск сразу не заладился. Любые попытки посмотреть через DevTools происхождение данных неминуемо натыкались на месиво из кода с плагинами для WordPress и другой засохшей кашей. Гугл также не спешил помогать: по запросу «aurora forecast json» встречались только гайды, pdf-файлы и открытия британских учёных.
Спустя время подметил, что под графиками один владелец сайта оставил сухой референс «NOAA». Поиск выявил, что аббревиатура расшифровывается как «Национальное управление океанических и атмосферных исследований США» — то место, откуда явно тянулись все следы. Любопытно, что никто не указывал ссылки на конкретные разделы с данными. Вероятно, чтобы каждый прошел по дебрям запутанного американского сайта и ощутил все прелести этого путешествия самостоятельно.
Мне открылся суровый мир американского государственного портала, где странности поджидали повсюду. Например, для получения трёхдневного прогноза американцы указали заветную ссылку, а для того же самого, но на 27 дней, такую ссылку не добавили, ищи как хочешь. Или ещё: прогнозов на три дня два, один называется «3-day forecast», а другой «3-day geomagnetic forecast», причем они почти идентичные. Что выбрать? Оказалось, что хотя логичнее отдать предпочтение «3-day geomagnetic forecast», он обновляется не так оперативно, как первый вариант, в среднем на день позже, поэтому выбрал первый — «3-day forecast».
Сами же данные, наоборот, содержались в небольших и вполне удобных таблицах, где в случае трёхдневного прогноза указали время, дату и КП-индекс на нужные часы. Сверху обычно содержится небольшой бриф по индексам за какое-то время, а снизу пояснения по таблице, но это не пригодилось в рамках моего ресурса.
Несколько поблуждав по американскому сайту, я всё же нашёл нужную страницу. Она оказалась простейшим списком со ссылками на любой вкус: для получения картинок, параметров Солнца и другого. Жаль, что найти её оказалось не так элементарно. При этом в NOAA SWPC предоставляют данные бесплатно, а отключают раздачу только если запросов непомерно много. Это я проверил случайно из-за живой перезагрузки по ходу написания кода, решилось временной сменой браузера.
Заключительная странность обнаружилась в том, что на найденной странице по пути «json» отсутствовали нужные мне прогнозы в этом формате. Все они оказались через ссылку ниже, — и это был «text».
Оформление страницы
Для сайта я выбрал простое и лаконичное название «Севернее». Порадовало, что этот домен — severnee.ru — оказался свободен, хоть в нём и нет ничего про сияние, зато легко держать в памяти и есть связь с географией регионов, где видно это необычное явление (UPD: на момент 2024 года сайт переехал на GitHub Pages и теперь живёт тут). Небольшой текст после заголовка быстро и «на пальцах» объясняет, как увидеть северное сияние и понять прогноз на странице. Клик по стрелке вниз плавно проводит к графикам.
Картинку рисовал сам (из кубиков в Paint) по аналогии с формой и цветами яркого сияния, а ещё не смог удержаться и наложил на неё анимацию в лице transform: rotate, чтобы она спокойно вращалась. Возможно, однажды уберу, но этот эффект меня завораживает, да и выглядит необычно. Кнопка «Подробнее» сейчас ведёт на сторонний сайт, это для тех, кто хочет лучше разобраться в теме. Сначала думал сделать свою контентную страничку, но потом решил сосредоточиться на прогнозе.
Основную часть разделил на четыре графика, три из которых представляют прогноз с интервалом в три часа, а последний общий на 27 дней. Вся работа над проектом сосредоточилась именно здесь: как над кодом для вывода данных, так и над посильным дизайном для их удобного просмотра.
Для графиков выбрал бесплатную библиотеку Chart.js, причём не сразу заметил, что к такому решению прибегнул не только я. Видимо, популярно и просто, хотя на некоторых ресурсах заверстали вывод индексов с помощью таблиц, но готовое решение посчитал красивее и проще. Под капотом Chart.js рендерит графики в тегах canvas.
Основной сложностью с этой библиотекой для меня выступила настройка конфигов, на основе которых формируется облик, цвета и всё остальное. Документация хорошая, но немало аспектов пришлось гуглить, причём нередко я обнаруживал, что какие-то места настроить вообще нельзя. Зато бесплатно.
Мне всегда казалось странным, что чем выше значение КП-индексов, тем более пёстрый цвет шкал выбирали на других проектах. Почему жёлтый и красный, если северное сияние обычно зелёное? Поэтому для низких индексов я окрасил столбцы в сероватый цвет — на самом деле, слабое сияние очень похоже на обычные облака. Чем выше индексы, тем более насыщенным будет зелёный, а при очень высоких значениях шкалы примут оттенки фиолетового, которые появляются и на самом деле.
Подписи к графикам наконец сделал на русском — теперь никакого контраста. Chart.js также позволяет вывести подсказку при наведении на графики, что показалось очень удобным, особенно для мобильной версии. В полной мере это ощущается в почасовых прогнозах, где можно вести курсором или пальцем вдоль линии данных и наблюдать, как КП-индексы меняются со временем.
В конце страницы добавил простейший тест для самопроверки, где можно выбрать либо «да», либо «нет». По его результатам в конце будут советы. Когда планируешь путешествие самостоятельно, часто возникает ощущение, что определенно что-то забыто. Такой тест как раз поможет проверить самые базовые пункты в подготовке, при соблюдении которых всё точно получится. Увидеть северное сияние на самом деле очень просто, и этот тест призван поддержать сомневающихся в своих силах.
Никаких пёстрых графиков, обилия непонятной информации и коктейля русского и английского, всё в рамках одной простой и удобной страницы с прогнозами. Как ни странно, сложнее всего оказалось не написать код для работы с данными, а сделать сайт комфортным для глаза, — на подбор цветов ушло особенно много времени, хотя, уверен, всё это далеко от идеала, но куда приятнее, чем красный столб посреди звёздного неба.
Немного кода под капотом
Пожалуй, главная часть — как всё это работает на JS. Обработку прогнозов NOAA можно разделить на три общих этапа: сперва fetch-запрос, затем подготовка данных к передаче графикам и наконец сам рендеринг графиков. Работа с прогнозами на три дня и на 27 дней немного различается, но первая часть общая, кроме URL-адреса:
try {
let response = await fetch('https://services.swpc.noaa.gov/text/3-day-forecast.txt') // Запрос трехдневного прогноза
let textForecast = await response.text() // Ответ в текстовом формате
let resultForecast = threeDayTablesFiller(textForecast) // Обработка прогнозов
daysChartBuilder(resultForecast) // Рендер графиков
} catch (error) {
console.log(`Ошибка в получении трёхдневного прогноза: ${error}`)
}
Помимо прогноза КП-индексов в ответе NOAA есть и другие данные, например, краткий бриф с самыми высокими индексами за некоторый промежуток времени. Поэтому во второй части — той, где подготавливаются данные, — сперва я вырезал нужную мне информацию из строки по-простому: от одного индекса до другого. На тот момент я не сомневался, что текущая запись прогнозов существуют ещё с незапамятных времён и столько же лет не поменяется, а те события с крушением графиков на всех сайтах ещё не успели произойти. Вид портала NOAA SWPC лишь подкреплял эти заблуждения.
Через пару дней я обнаружил, что NOAA иногда добавляет справа от КП-индексов такие записи, как «G1», что указывает на геомагнитную бурю в это время. Если КП-индексы ниже пяти, то таких «добавок» нет. Из-за этого длина строки увеличивалась, а цикл, который вытаскивал нужную информацию, съезжал на несколько символов, поэтому в графики попадало не то, что нужно. Но тогда я всё равно посчитал, что это просто особенность, и лишь добавил проверку на эти «G1», чтобы в случае их появления чистить строку от ненужных мне данных.
Это была ошибка, — американцы не противились пробовать новенькое, и скоро они отказались от целых значений КП-индексов и добавили им точность до сотых. Строка снова длиннее — в графиках снова не то. Правда, в тот день, как я писал выше, упали графики у всех сайтов с прогнозами северного сияния, видимо, не я один убеждал себя в консервативности NOAA. После этого решил сделать гибкий поиск начала и конца нужных данных в таблице через RegExp.
const startOfForecastTable = new RegExp('(00-03UT)', 'g') // Находит начало таблицы с KP-индексами
const endOfForecastTable = new RegExp('\n\nRationale', 'g') // Находит конец таблицы
const extraSignatures = new RegExp('[(]G[1-5][)]', 'g') // Находит лишние подписи, как «(G1)»
const indexOfStart = data.search(startOfForecastTable)
const indexOfEnd = data.search(endOfForecastTable)
let crudeForecast = data.slice(indexOfStart, indexOfEnd) // Вырезаем таблицу из response.text()
if (crudeForecast.includes('G')) {
crudeForecast = crudeForecast.replaceAll(extraSignatures, ' ') // Пробелы нужны, чтобы удалить элемент без сдвига
}
const resultForecast = crudeForecast
.split(' ') // Создаём массив из таблицы для удобства
.filter(item => item !== '')
После того, как массив с общим прогнозом на три дня готов, в цикле данные из него копируются в другие массивы для хранения прогнозов уже по дням. В дальнейшем они попадают в функцию для рендеринга графиков в другом файле. Три пустых массива, три цикла для каждого.
const firstDayChartData = new Array()
const secondDayChartData = new Array()
const thirdDayChartData = new Array()
for (let i = 1; i < 30; i += 4) {
firstDayChartData.push(Number(resultForecast[i]))
}
for (let j = 2; j < 31; j += 4) {
secondDayChartData.push(Number(resultForecast[j]))
}
for (let c = 3; c < 32; c += 4) {
thirdDayChartData.push(Number(resultForecast[c]))
}
return { firstDayChartData, secondDayChartData, thirdDayChartData } // Возвращаем готовые почасовые прогнозы на три дня для графиков
С прогнозом на 27 дней почти тоже самое, вся разница в том, что там требуется перевести на русский язык подписи для графиков, — для этого сделал импровизированный объект с английскими и русскими названиями месяцев. Методом .map() каждая дата в виде «2022 Jan 01» очищается от года и переводится на русский, а в конце преобразованный массив лейблов и массив, полный КП-индексами, передаются далее для рендеринга графиков.
const MONTHS_TRANSLATION = {
Jan: 'Янв',
Feb: 'Фев',
Mar: 'Мар',
Apr: 'Апр',
May: 'Май',
Jun: 'Июн',
Jul: 'Июл',
Aug: 'Авг',
Sep: 'Сен',
Oct: 'Окт',
Nov: 'Ноя',
Dec: 'Дек'
}
const translatedLabels = monthChartLabels.map(date => {
const engMonth = date.slice(5, 8)
const ruMonth = MONTHS_TRANSLATION[engMonth]
return date.slice(5).replace(engMonth, ruMonth) // Очищаем от года, переводим название месяца на русский язык
})
return { monthChartData, translatedLabels } // Возвращаем готовый прогноз и лейблы
На последнем этапе обработанные данные в массивах разбираются на отдельные переменные, например, первый день, второй день, третий. После этого они попадают в конфиг графиков и рендерятся вызовом new Chart(). Я не стал приводить внутренности конфигов, потому что они достаточно объемные, но посмотреть их полностью можно в репозитории Гитхаба.
module.exports.daysChartBuilder = function (forecasts) {
const { firstDayChartData, secondDayChartData, thirdDayChartData } = forecasts
const config1 = {} // Прототип конфигов для графиков
const config2 = Object.create(config1, {}) // Создаём экземпляры прототипа config1 и перезаписываем data
const config3 = Object.create(config1, {})
try { // Рендеринг графиков на основе конфигов
new Chart(
document.getElementById('first-day-chart'),
config1
)
new Chart(
document.getElementById('second-day-chart'),
config2
)
new Chart(
document.getElementById('third-day-chart'),
config3
)
} catch (error) {
console.log(`Ошибка рендеринга графиков на три дня: ${error}`)
}
}
Функция для рендеринга графика на 27 дней выглядит также, разве что переменные — это КП-индексы и лейблы на русском. Весь проект собирается на Parcel, хотя сперва я подключил Webpack, но позже передумал, поскольку для такого небольшого ресурса Parcel куда проще, удобнее и лаконичнее.
Настоящее и будущее
На текущий момент сайт работает примерно два с половиной месяца. Первое время я даже не заходил в метрики, чтобы понять, пользуются ли им вообще люди, поскольку не питал иллюзий, что подниматься в поиске он будет долго. Но позже всё-таки решил проверить и не зря: оказалось, что ресурс находится среди трёх первых сайтов выдачи по запросу «прогноз северного сияния» в Яндексе, а иногда поднимается на второе и даже первое место.
Конечно, я не знаю, сколько человек из статистики Вебмастера поехали в путешествие, узнав прогноз на моём сайте, но если это сделал хотя бы один человек и ему было комфортно, то это классно и идея реализована.
Не сомневаюсь, что код можно улучшать и улучшать, а ещё добавить к проекту что-нибудь новое, например, продвинутый прогноз в красивой форме, такого пока никто не делал. Возможно, чуть позже вернусь к коду и все-таки что-то добавлю, но сейчас для меня главное, что он спокойно работает и, наверняка, помогает спланировать чьё-то путешествие. Проект полностью доступен на Гитхабе и любой желающий может взять за основу идею или делать вообще что угодно.
P.S. Ссылка на репозиторий GitHub.