diff --git a/README.md b/README.md index 4638c655..3ef599c7 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,7 @@ This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks. If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io. + +This is a simple weather application which will display the current weather and a five-day forecast for any geographic location based on an input of City, State (if applicable) and Country. The application will begin with the user's current location as a default. + +Data is taken from Open Weather API: https://openweathermap.org/api. diff --git a/index.html b/index.html new file mode 100644 index 00000000..679ab8ac --- /dev/null +++ b/index.html @@ -0,0 +1,55 @@ + + + + + Weather Project + + + + + + + +
+
+ +
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ +
+
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 00000000..d188bf75 --- /dev/null +++ b/main.js @@ -0,0 +1,285 @@ +const apiKey = "6427275c4ee8b157888fdf144b2fc5ca"; +const units = "imperial"; + +// Fetch current cocation to display default weather forecast.This returns an object in the form of {lat:____, lon:____} +const getCurrentLocation = () => { + const options = { + enableHighAccuracy: true, + maximumAge: 0, + }; + + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition( + (position) => { + resolve({ + lon: position.coords.longitude, + lat: position.coords.latitude, + }); + }, + (error) => reject(console.warn(`ERROR(${error.code}): ${error.message}`)), + options + ); + }); +}; + +// Using the current location, fetch the appropriate weather data from OpenWeather API +const fetchCurrentLocationData = async (location) => { + const url = `https://api.openweathermap.org/data/2.5/weather?lat=${location.lat}&lon=${location.lon}&appid=${apiKey}&units=${units}`; + + const fetchedData = await fetch(url, { + method: "GET", + data: "json", + }); + + const currentLocationData = await fetchedData.json(); + + return currentLocationData; +}; + +// Update HTML with current/default location weather data +const addCurrentLocation = async () => { + try { + const location = await getCurrentLocation(); + const locationData = await fetchCurrentLocationData(location); + addNow(locationData); + + const fiveDayData = await fetchFiveDayData(location); + const processedData = await processFiveDayData(fiveDayData); + addFiveDay(processedData); + } catch (error) { + console.error("Error processing data:", error); + } +}; + +addCurrentLocation(); + +// Use fetchNow() and addNow() functions to update the current weather forecast section of the HTML from a given locaiton query +const getNow = async () => { + try { + const nowData = await fetchNow(); + addNow(nowData); + } catch (error) { + console.error("Error processing data:", error); + } +}; + +// Fetch current weatehr data for the location queried +const fetchNow = async () => { + const query = document.querySelector("#query").value.replace(/\s+/g, "%20"); + + const url = `https://api.openweathermap.org/data/2.5/weather?q=${query}$&limit=1&appid=${apiKey}&units=${units}`; + + const fetchedData = await fetch(url, { + method: "Get", + dataType: "json", + }); + + const nowData = await fetchedData.json(); + + return nowData; +}; + +// Use template to update HTML for the current weather forecast based on data from the queried location +const addNow = async (data) => { + const nowSection = document.querySelector("#now"); + nowSection.replaceChildren(); + + const template = ` +
+
+

${data.main.temp.toFixed(1)}°

+

${data.name}

+
${data.weather[0].main}
+
+
+ unable to load image +
+
+ `; + + nowSection.insertAdjacentHTML("beforeend", template); +}; + +// Use a series of functions to populate the 5-day forecast section of the HTML +const getFiveDay = async () => { + try { + const coordinates = await fetchCoordinates(); + const fiveDayData = await fetchFiveDayData(coordinates); + const processedData = await processFiveDayData(fiveDayData); + addFiveDay(processedData); + } catch (error) { + console.error("Error processing data:", error); + } +}; + +// Use Open Weather Geolocation API to return data, {lat:____, lon:___}, for the queried location +const fetchCoordinates = async () => { + const query = document.querySelector("#query").value.replace(/\s+/g, "%20"); + + const coordinatesCall = await fetch( + `https://api.openweathermap.org/geo/1.0/direct?q=${query}&limit=1&appid=${apiKey} + `, + { + method: "GET", + dataType: "json", + } + ); + + const coordinatesArray = await coordinatesCall.json(); + const coordinates = coordinatesArray[0]; + + return coordinates; +}; + +// Given an object with {lat:___, lon:___} syntax, fetch five-day weather information. This returns 40 instances of data which must be divided into 5 sets of 8. +const fetchFiveDayData = async (coordinates) => { + const fiveDayURL = `https://api.openweathermap.org/data/2.5/forecast?lat=${coordinates.lat}&lon=${coordinates.lon}&appid=${apiKey}&units=${units}`; + + const fetchFiveDay = await fetch(fiveDayURL, { + method: "GET", + dataType: "json", + }); + + const fiveDayData = await fetchFiveDay.json(); + + return fiveDayData; +}; + +// Colate data from each day in the five-day forecast. +const processFiveDayData = async (data) => { + const dividedDayData = []; + + const processDayData = (day) => { + const indexRange = [(day - 1) * 8, day * 8 - 1]; + + const getAvgTemp = () => { + let acc = 0; + let timeIndex = 0; + + for (let i = indexRange[0]; i <= indexRange[1]; i++) { + const currentTemp = data.list[i].main.temp; + acc += (currentTemp - acc) / (timeIndex + 1); + timeIndex++; + } + + return acc.toFixed(1); + }; + + const getSummaryIcon = () => { + let icons = {}; + + for (let i = indexRange[0]; i <= indexRange[1]; i++) { + const icon = data.list[i].weather[0].icon; + + if (icons.hasOwnProperty(icon)) { + icons[icon]++; + } else { + icons[icon] = 1; + } + } + + let iconMaxCount = 0; + let iconSummary = ""; + + for (let key in icons) { + if (icons[key] > iconMaxCount) { + iconMaxCount = icons[key]; + iconSummary = key; + } + } + return iconSummary; + }; + + const getDayName = () => { + const dt_txt = new Date(data.list[indexRange[0]].dt_txt); + + const dateFormatter = new Intl.DateTimeFormat("en-US", { + weekday: "long", + }); + + return dateFormatter.format(dt_txt); + }; + + const getSummaryWeatherDescription = () => { + let dayData = {}; + let weatherDescriptions = {}; + + for (let i = indexRange[0]; i <= indexRange[1]; i++) { + const weatherDescription = data.list[i].weather[0].main; + + if (weatherDescriptions.hasOwnProperty(weatherDescription)) { + weatherDescriptions[weatherDescription]++; + } else { + weatherDescriptions[weatherDescription] = 1; + } + } + + let weatherDescriptionsMaxCount = 0; + let summaryDescription = ""; + + for (let key in weatherDescriptions) { + if (weatherDescriptions[key] > weatherDescriptionsMaxCount) { + weatherDescriptionsMaxCount = weatherDescriptions[key]; + summaryDescription = key; + } + } + return summaryDescription; + }; + + return { + avgTemp: getAvgTemp(), + iconSummary: getSummaryIcon(), + weatherSummary: getSummaryWeatherDescription(), + dayName: getDayName(), + }; + }; + + // Iterate through each day to colate all summary data + for (let i = 1; i <= 5; i++) { + dividedDayData.push(processDayData(i)); + } + + return dividedDayData; +}; + +// Update HTML with data given in the form returned by the processFiveDayData function +const addFiveDay = (summaryData) => { + const fiveDaySection = document.querySelector(".forecast-5-day"); + fiveDaySection.replaceChildren(); + + const dayTemplate = (summaryItem) => ` +
+
+

${summaryItem.weatherSummary} +

+

${summaryItem.avgTemp}° +

+ +
${summaryItem.dayName}
+
+
+ `; + + const allDaysTemplate = ` +
+

Five Day Forecast

+
+ +
+ ${summaryData.map((summaryItem) => dayTemplate(summaryItem)).join("")} +
+ `; + + fiveDaySection.insertAdjacentHTML("beforeend", allDaysTemplate); +}; + +const searchButton = document.querySelector(".search"); +searchButton.addEventListener("click", () => { + getNow(); + getFiveDay(); + document.querySelector("#query").value = ""; +});