Содержание
В дополнение к другим вариантам использования OpenWeatherMap API добавим ещё один вариант с конкретной функцией выбора города. Приложение прогноз погоды на 5 дней будет работать с использованием вызова API по названию города api.openweathermap.org/data/2.5/forecast?q= {enjcity name}, {код страны}.
Приложение прогноз погоды: что будем делать
Для начала сделаем приложение монофункциональным, то есть только прогноз погоды на 4 дня (вполне достаточно) с возможностью выбора любого города либо из выпадающего списка, либо путем ввода в поле input.
Сразу обозначим проблемы:
- Использование ввода названия города/населенного пункта влечет несколько проблем, основные из которых — это необходимость ввода названия на английском (что пользователь не всегда сможет корректно сделать) и наличие в мире нескольких населенных пунктов с одинаковым названием, но в разных координатах. Чтобы избежать такой путаницы, в документации рекомендуется вызывать API по идентификатору города, чтобы получить однозначный результат. Но имеющиеся файлы (http://bulk.openweathermap.org/sample/) огромные, и работать с ними нецелесообразно. Для небольших виджетов-проектиков этим, скорее всего, не занимаются.
- Использование списка ограничено тем самым списком, который разработчик посчитает нужным предложить для выбора пользователю. Обычно этот вариант используют для показа погоды для одного-нескольких конкретных городов.
Из этого вытекают 2 основные задачи:
- для поля input реализовать проверку ошибок запроса, если пользователь ввёл неверное название (точнее, несуществующее) и сервер возвратил 404.
- для списка — просто вручную его создать.
Кроме того, для AJAX запроса будут использоваться данные, полученные из поля input (будет только 1 запрос) , поэтому на тот случай, если пользователь воспользуется выпадающим списком, необходимо связать поле input и select (вставить данные из select в input).
Чтобы стало понятнее, чего мы хотим добиться, покажем, как это работает в конечном результате:
Вот здесь можно попробовать:
Введите название города
(на английском)
Например: Kharkiv, Kyiv, Tokio,
Moscow, Saint Petersburg, Tver ...
Или выберите из списка (25 городов)
Прогноз на 4 дня
Опция выбора из списка больше похожа на подсказку для пользователя (если он по каким-либо причинам испытывает трудности с вводом английского названия). В видимой части мы прописываем название на кириллице, а в значении value пишем валидное название на английском. Именно его вставляем в поле input и уже оттуда берем его для отправки в запросе.
Вот исходный код:
HTML
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="style.css"> </head> <body> <div class="left"> <h2>Введите название города</h2> <p>Например: <span style="color:green; font-size:16px;">Kharkiv, Kyiv, Tokio, <br>Moscow, Saint Petersburg, Tver ...</span></p> <input type="text" id="city" placeholder="Введите название города (англ.)..."/> <button id="cityB" onclick="showDateTime();">OK</button> <h2>Или выберите из списка (25 городов)</h2> <p><select class="select" name="cityes"> <option selected value="Kyiv">Киев</option> <option value="Kharkiv">Харьков</option> <option value="Lviv">Львов</option> <option value="Moscow">Москва</option> <option value="Tver">Тверь</option> <option value="Stavropol">Ставрополь</option> <option value="Chelyabinsk">Челябинск</option> <option value="Minsk">Минск</option> <option value="Astana">Астана</option> <option value="Tbilisi">Тбилиси</option> <option value="Chisinau">Кишинёв</option> <option value="Berlin">Берлин</option> <option value="Rio de Janeiro">Рио-де-Жанейро</option> <option value="Beijing">Пекин</option> <option value="Melbourne">Мельбурн</option> <option value="Managua">Манагуа</option> <option value="Madrid">Мадрид</option> <option value="Helsinki">Хельсинки</option> <option value="Warsaw">Варшава</option> <option value="Ottawa">Оттава</option> <option value="London">Лондон</option> <option value="Rome">Рим</option> <option value="Mexico city">Мехико</option> <option value="Delhi">Дели</option> <option value="Tokio">Токио</option> </select></p> <p class="text-center">Прогноз на 4 дня <br><span id="cityC"></span><p> <div id="tablo"> <div class="day"> <img id="tmp4"/><br> <p id="day1"></p> <div id="demo7"></div> <div id="demo5" ></div> <div id="demo6"></div> <div id="demo4"></div> </div> <div class="day"> <img id="tmp5"/><br> <p id="day2"></p> <div id="demo10"></div> <div id="demo11"></div> <div id="demo8"></div> <div id="demo9"></div> </div> <div class="day"> <img id="tmp6"/><br> <p id="day3"></p> <div id="demo14"></div> <div id="demo15"></div> <div id="demo12"></div> <div id="demo13"></div> </div> <div class="day"> <img id="tmp7"/><br> <p id="day4"></p> <div id="demo18"></div> <div id="demo19"></div> <div id="demo16"></div> <div id="demo17"></div> </div> </div> </div> <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script> <script src="js2.js"></script> </body> </html>
CSS:
.day { border-radius:4px; box-shadow: 0 0 10px rgba(0,0,0,0.5); text-align:center; padding:20px; margin-bottom: 20px; width:auto; } .day, #demo { background-image: linear-gradient(141deg, #9fb8ad 0%, #6600FF 45%, #6600CC 75%); } #tablo { display: none; flex-flow: row wrap; align-items: stretch; justify-content: space-around; } #day1, #day2, #day3, #day4 { font-size:1.8em; color:#e8ffe8; text-shadow: 0 0 5px #070707; } #demo7, #demo6, #demo10, #demo8, #demo14, #demo12, #demo18, #demo16 { font-size:.8em; color:#FF66FF; } #demo5, #demo11, #demo15, #demo19 { font-size:1.3em; color:#0768f0; } #demo4, #demo9, #demo13, #demo17 { font-size:1.3em; color:#ff00bb; } input{ font-size:16px; font-weight:bold; font-style:italic; border: solid 2px #68bcff; outline: 0; width: 30%; height: 36px; border-top-left-radius: 50px; border-bottom-left-radius: 50px; padding-left: 40px; margin: 10px 0px; z-index: 9999999; } #cityB { height: 40px; width: 40px; border: solid 2px #68bcff; border-left: none; outline: 0; border-top-right-radius: 50px; border-bottom-right-radius: 50px; padding-right: 5px; margin-left:-6px; background: #68bcff; color: #fff; } .text-center { background: radial-gradient(circle at 65% 15%, #6699FF, #660066); font-family:Open Sans; font-size:32px; color: #ffffff; text-shadow: 1px 1px 2px black, 0 0 1em #ffffff; text-align:center; } #cityC { color:#8df2a1; font-size:26px; padding:10px; } .select { display: block; font-size: 16px; font-family: sans-serif; font-weight: 700; color: #444; line-height: 1.3; padding: .6em 1.4em .5em .8em; width: 100%; max-width: 200px; box-sizing: border-box; margin: 0; border: 1px solid #aaa; box-shadow: 0 1px 0 1px rgba(0,0,0,.04); border-radius: .5em; -moz-appearance: none; -webkit-appearance: none; appearance: none; background-color: #fff; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'), linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%); background-repeat: no-repeat, repeat; background-position: right .7em top 50%, 0 0; background-size: .65em auto, 100%; } .select::-ms-expand { display: none; } .select:hover { border-color: #888; } .select:focus { border-color: #aaa; box-shadow: 0 0 1px 3px rgba(59, 153, 252, .7); box-shadow: 0 0 0 3px -moz-mac-focusring; color: #222; outline: none; } .select option { font-weight:normal; } *[dir="rtl"] .select, :root:lang(ar) .select, :root:lang(iw) .select { background-position: left .7em top 50%, 0 0; padding: .6em .8em .5em 1.4em; } @media all and (max-width: 900px){ input{ font-size:12px; font-weight:normal; font-style:italic; border: solid 1px #68bcff; outline: 0; width: 70%; height: 30px; } button{ height: 30px; width: 30px; border: solid 1px #68bcff; } }
JavaScript/jQuery:
$(function(){ let input = $('#city'), inpVal = input.val(); $('.select').on('change', function(){ input.val(inpVal + $(this).val()); }); }); $('#cityB').on('click', function(){ $('#tablo').css('display','flex'); var city=$('#city').val(); //Здесь вам нужно вставить свой бесплатный ключ var apiURI2 = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=здесь вам нужно вставить свой код`; console.log("success getWeather2"); console.log(apiURI2); $.ajax({ url: apiURI2, dataType: "jsonp", type: "GET", async: "true", timeout : 500, success : function(data) { console.log("Success"); }, error : function(e) { console.log("Error"); $('#cityC').html('<p style="color:red";>ERROR</p><p style="color:#bef7f1";>Проверьте корректность названия</p>'); $('#tablo').css('display','none'); }, done : function(e) { console.log("DONE"); }, }).done(dataHandler3); $('#cityC').text('в' + ' ' + city); function dataHandler3(data) { dataString = JSON.stringify(data); var now = new Date(); let h = now.getHours(); var num = 8-(Math.floor(h/3)); //завтра document.getElementById("demo6").innerHTML = data.list[num+3].dt_txt; document.getElementById("demo4").innerHTML = "Макс." + " " + Math.floor((data.list[num+3].main["temp"])-273,15)+"°C"; document.getElementById("demo7").innerHTML = data.list[num].dt_txt; document.getElementById("demo5").innerHTML = "Мин." + " " + Math.floor((data.list[num].main["temp"])-273,15)+"°C"; var imgURL = "https://openweathermap.org/img/w/" + data.list[num+3].weather[0].icon + ".png"; $("#tmp4").attr("src", imgURL); //послезавтра document.getElementById("demo8").innerHTML = data.list[num+11].dt_txt; document.getElementById("demo9").innerHTML = "Макс. " + " " + Math.floor((data.list[num+11].main["temp"])-273,15)+"°C"; document.getElementById("demo10").innerHTML = data.list[num+8].dt_txt; document.getElementById("demo11").innerHTML = "Мин." + " " + Math.floor((data.list[num+9].main["temp"])-273,15)+"°C"; var imgURL = "https://openweathermap.org/img/w/" + data.list[num+11].weather[0].icon + ".png"; $("#tmp5").attr("src", imgURL); //после-послезавтра document.getElementById("demo12").innerHTML = data.list[num+19].dt_txt; document.getElementById("demo13").innerHTML = "Макс." + " " + Math.floor((data.list[num+19].main["temp"])-273,15)+"°C"; document.getElementById("demo14").innerHTML = data.list[num+16].dt_txt; document.getElementById("demo15").innerHTML = "Мин." + " " + Math.floor((data.list[num+17].main["temp"])-273,15)+"°C"; var imgURL = "https://openweathermap.org/img/w/" + data.list[num+19].weather[0].icon + ".png"; $("#tmp6").attr("src", imgURL); //после-после-послезавтра document.getElementById("demo16").innerHTML = data.list[num+27].dt_txt; document.getElementById("demo17").innerHTML = "Макс." + " " + Math.floor((data.list[num+27].main["temp"])-273,15)+"°C"; document.getElementById("demo18").innerHTML = data.list[num+24].dt_txt; document.getElementById("demo19").innerHTML = "Мин." + " " + Math.floor((data.list[num+25].main["temp"])-273,15)+"°C"; var imgURL = "https://openweathermap.org/img/w/" + data.list[num+27].weather[0].icon + ".png"; $("#tmp7").attr("src", imgURL); } }); //Показ дни недели function showDateTime() { var d = new Date(); var n1, n2, n3, n4, n5; var weekday = new Array(7); weekday[0] = "Воскресенье"; weekday[1] = "Понедельник"; weekday[2] = "Вторник"; weekday[3] = "Среда"; weekday[4] = "Четверг"; weekday[5] = "Пятница"; weekday[6] = "Суббота"; if(d.getDay() >= 3){ n1 = weekday[(d.getDay()+1)]; n2 = weekday[(d.getDay()+2)]; n3 = weekday[(d.getDay()+3)]; n4 = weekday[7-(d.getDay()+4)];} if(d.getDay() >= 4) { n1 = weekday[(d.getDay()+1)]; n2 = weekday[(d.getDay()+2)]; n3 = weekday[7-(d.getDay()+3)]; n4 = weekday[9-(d.getDay()+4)];} if(d.getDay() >= 5) { n1 = weekday[(d.getDay()+1)]; n2 = weekday[7-(d.getDay()+2)]; n3 = weekday[9-(d.getDay()+3)]; n4 = weekday[11-(d.getDay()+4)];} if(d.getDay() >= 6) { n1 = weekday[7-(d.getDay()+1)]; n2 = weekday[9-(d.getDay()+2)]; n3 = weekday[11-(d.getDay()+3)]; n4 = weekday[13-(d.getDay()+4)];} if(d.getDay() < 3) { n1 = weekday[(d.getDay()+1)]; n2 = weekday[(d.getDay()+2)]; n3 = weekday[(d.getDay()+3)]; n4 = weekday[(d.getDay()+4)]; } document.getElementById("day1").innerHTML = n1; document.getElementById("day2").innerHTML = n2; document.getElementById("day3").innerHTML = n3; document.getElementById("day4").innerHTML = n4; } showDateTime();
По поводу ключа. Используйте свой, поскольку у бесплатного ключа есть ограничения по количеству запросов (в минуту не более 60 — чего вполне достаточно). Не хотелось бы на него много навешивать. Тем более что процедура его получения — дело 1 минуты. Всё просто, «как двери»: достаточно зарегистрироваться (перейдите по ссылке) и получить код.
Добавляем функциональность
Теперь, когда все работает, можно исправить ошибки и расширить функциональность приложения. Например, поставим задачу показать на карте выбранный город. Для этого будем использовать карты Open Street Map и Leaflet API. Получать координаты выбранного города будем при помощи API Nominatim.
Также добавим запрос текущей погоды (current weather data) и местоположение по IP.
Для множественного выбора вариант с select — options, который мы сделали вначале, не подойдет, поэтому перейдем на списки ul-li такого формата:
<li class="first"><span value="Черновицкая область" class="k">Черновицкая обл.</span> <ul id="select2">Города <li> <span class = "s" value = "Vashkivtsi, Chernivets’ka Oblast’, UA">Вашковцы</span></li> <li> <span class = "s" value = "Vyzhnytsia, UA">Вижница</span></li> <li> <span class = "s" value = "Hertsa, UA">Герца</span></li> <li> <span class = "s" value = "Zastavna, UA">Заставна</span></li> <li> <span class = "s" value = "Kitsman, UA">Кицмань</span></li> <li> <span class = "s" value = "Novodnistrovsk, UA">Новоднестровск</span></li> <li> <span class = "s" value = "Novoselytsya, UA">Новоселица</span></li> <li> <span class = "s" value = "Sokyriany, Chernivets’ka Oblast’, UA">Сокиряны</span></li> <li> <span class = "s" value = "Storozhynets, UA">Сторожинец</span></li> <li> <span class = "s" value = "Khotyn, UA">Хотин</span></li> <li> <span class = "s" value = "Chernivtsi, UA">Черновцы</span></li> </ul> </li>
Забегая наперед скажем, что обратное геокодирование и показ геолокации на карте не всегда завершается sucsess, так как населенных пунктов c одинаковым названием может быть несколько. В этом случае сервер возвращает пустой файл. Поэтому можно использовать фрейм, который вставляется в страницу при получении пустого ответа (для этого проверяем при помощи инструкции if(d.length == 0){console.log(‘ERROR’);}else{}; длину полученного json файла), в котором есть поле для выбора нужного населенного пункта из списка найденных для отображения на карте.
Можно избежать такой необходимости, поскольку есть возможность делать уточнённый запрос (мы использовали запрос с таким синтаксисом: https://nominatim.openstreetmap.org/search/${city}?format=json&addressdetails=1&limit=1&polygon_svg=1,
а уточнённый выглядит так: https://nominatim.openstreetmap.org/search/${country}+${oblast}+${city}?format=json&addressdetails=1&limit=1&polygon_svg=1.)
Но поскольку мы используем одни и те же данные, взятые из поля input для запроса погоды и координат, тогда карта отображаться будет точно, а погода — часто отвечает 404. Поэтому от уточненного запроса мы отказались, так как пришлось бы пересмотреть всю логику работы приложения. Атрибут value в списках как раз будет использоваться для более точного запроса и снижения к минимуму вариабельности результата поиска. Вдобавок ко всему иногда есть отличия в используемом синтаксисе, по которому можно найти город в https://nominatim.openstreetmap.org/search/ и для него же погоду на https://openweathermap.org/find. И напоследок — варианты переименованных городов, с какими на разных сервисах путаница. Поэтому чтобы привести всё в соответствие пришлось поломать голову. Вот, что получилось:
При реализации карт пришлось решать проблему: после первого запроса при каждом последующем выборе карта не обновляется. Решили вопрос таким способом: контейнер для карт вставлять каждый раз заново по клику кнопки ОК :
$('#map2').replaceWith('<div id="map2" style="height:500px;width:80%;"></div>');
Также не нашлось вариантов с переводом описания погоды (типа оvercast cloud), что пришлось решать таким способом: на сайте скопировали все варианты описания (Weather condition codes), создали из них массив, затем перевели всё это (что,тоже проблематично в плане правильной интерпретации Shower sleet, heavy intensity shower rain и т.д.) и создаём массив такой же длины. Затем сравниваем полученный ответ описания с первым массивом, получаем его код и вставляем в страницу уже свой перевод из второго массива с таким же кодом:
var znak = ['overcast clouds','clear sky','...']; var znakRU = ['Пасмурно','Чистое небо','...']; //затем после получения данных..... var m = znak.indexOf(data.weather[0].description); document.getElementById("#id").innerHTML = "Погода: " + " " + znakRU[m];
В остальном — чисто технические детали, связанные с реализацией отображения страницы и всех её интерактивных элементов. Приложение прогноз погоды построено с использованием многих блоков из уже имеющихся (в примерах по ссылке в начале статьи, а также в статье, посвященной бесплатным API карт).

Почасовое отображение прогноза погоды
Если попробовть извлечь максимум пользы от получаемого json, то можно реализовать почасовый вывод погоды для нашего приложения.
Со структурой json мы разобрались: в нем 40 строк (ключей) list, в каждой из которых значение представляет объект. В нем — также по несколько строк, значение которых также может быть в виде объекта.
Получить эти данные несложно, но проблема оказалась в другом: в периодичности обновления файла на сервере openweather. В предыдущем примере наше приложение получает данные через фиксированный промежуток — из каждой восьмой строки. Мы взяли время 12.00 и 03.00. Хотя изначально при наблюдении за работой приложения в течение определенного времени заметили, что выводимые данные то совпадают с заданными критериями поиска (выводит именно в 12 и 3 часа), то иногда «сползают» на 1 строку вперёд (выводит 6 и 15 часов). Казалось бы, наш алгоритм работы с массивом правильный, тогда почему данные «плавают»? Наше «приложение прогноз погоды» для одного дня валидность от этого не потеряет, но если выводить погоду для каждых 3-х часов, такое положение вещей не годится.
Логика получения значения нужной нам строки простая: получаем часы текущего времени:
var d = new Date(); d.getHours();
затем получаем текущий 3-х часовый промежуток:
var K = Math.floor((d.getHours()/3))
и получаем количество промежутков до начала и конца каждых следующих суток:
var LAST = (14-(K)); var FIRST = (7-(K));
Проблему, почему плавают часы, решилась также просто: сравнив файлы, которые получаем, например, в 15:10 и 17:10 обнаруживается, что в первом случае мы получаем json, в котором ещё есть строка с погодой на 15:00 текущего дня, а в 17:10 уже получаем файл с первой строкой 18:00. Понятно, что наш алгоритм сползет на 1 строку и для следующего дня с 17:10 до 18:00 будет получать данные уже для 18: 00. А после 18:00 снова начнет корректно отображать прогноз, а ближе к 21:00 снова сползет на 1 час.
Таким образом, нам нужно просто сравнивать дату из первой строки json с текущей датой, и если текущая меньше, то от коэффициента отнимать 1.
var g = new Date(data.list[0].dt_txt); if(d < g){ K = Math.floor((d.getHours()/3))}else{K = Math.floor((d.getHours()/3)-1)};
Этот нюанс будет полезен тем, кто впервые решил попробовать поработать с openveather API и столкнулся с такой же проблемкой.
Кроме того, чтобы получить минимальную и максимальную температуру, уже недостаточно выводить температуру в 3 часа ночи (как минимальную) и в 12 дня (как максимальную), как мы поступили в предыдущем варианте. Днём может быть холоднее, чем ночью! Чтобы не гадать, просто создадим массив из показателей каждого дня из 8 элементов и из них выберем наибольшее и наименьшее значение:
min1 = arr1[0]; max = mi1; for (i = 1; i < arr.length; ++i) { if (arr[i] > max) max = arr[i]; if (arr1[i] < min) min = arr[i]; }
В итоге получили финальное приложение с почасовым прогнозом погоды, с конвертером валют (как же без него), возможностью выбора города и показом его на карте, в пустое место в блоке текущего дня мы вставили рандомный канвас (можно помедитировать):
Скачать полный вариант можно в форме ниже:
(в коде вставлены ключевые комментарии)

Финальный вариант приложения с почасовым прогнозом погоды, с конвертером валют, возможностью выбора города и показом его на карте.
Убедительная просьба использовать свой API Key!
Только прогноз погоды
Вариант полной версии приложения с одним только почасовым прогнозом, без конвертера (кроме удаления конвертера, добавлен красивый фон и применен эффект css mix-blend-mode к тексту):
Читайте больше по теме:
Популярные записи
- Сухая мозоль со стержнем и другие омозолелости: лечение - 92130
- Лекарства от тахикардии при пониженном давлении: медикаменты и народные средства - 91143
- Ночное сердцебиение: причины, симптомы, лечение - 87608
- Задания 1-й недели курса CS50 (Hello, Mario, Cash, Credit) - 17826
- Микоплазмоз: эффективные лекарства для лечения - 16222
- Дизентерия: причины, симптомы, лекарства, профилактика - 14992
- Сердечный кашель: причины, отличительные признаки - 13444
- Приложение прогноз погоды на JS с выбором города - 10882
- Первые практические задачи по HTML-разметке - 10607
- Интеграция JavaScript с Python и Flask - 9213
- Тест на знание JavaScript начального уровня, 50 вопросов - 8315
- JavaScript: примерный план изучения для начинающих - 7502
- Слайдер, карусель на Java Script для сайта - 6690
- Проктит и парапроктит: симптомы и лечение, диета - 6534
- Задания третьей недели курса CS50 (Whodunit, Resize легкий, Resize, Recover) - 6456
- Цистит: препараты для однократного приема - 6017
- PRO100 и Cutting: проектирование и раскрой - 5737
- Бегущая строка. Чем заменить HTML-тег marquee - 5588
- 10 лучших языков для мобильной разработки - 5299
- Задания второй недели курса CS50 (Caesar, Vigenere) - 5292
- Стрептодермия - что это, мази и препараты для лечения - 5182
- Диабетическая кома: виды, причины, первая помощь - 5016
- Уровень тревожности: тест Ч. Д. Спилбергера и Ю. Л. Ханина - 4983
- Видеопоток: GetUserMedia API, запись, загрузка - 4800
- Прошивка китайских Android смартфонов на базе MTK - 4794
Ух ты круто) очень пригодилось)) а то не успели в учаке нам Js дать как делайте выборку данных с открытого API
Жаль нету видео с объяснением как это работает((