1
0

ClimaCell API v4 (#8)

* gets lan/lon from map geo

* climacell v4 endpoints

* Uses ClimaCell API v4, adds Sunrise-Sunset API
This commit is contained in:
Eric
2021-01-28 19:46:11 -06:00
committed by GitHub
parent 6456d30dba
commit 5882f41615
12 changed files with 187 additions and 135 deletions
+9 -9
View File
File diff suppressed because one or more lines are too long
+57 -20
View File
@@ -48,6 +48,8 @@ export function AppContextProvider({ children }) {
const [customLat, setCustomLat] = useState(null);
const [customLon, setCustomLon] = useState(null);
const [mouseHide, setMouseHide] = useState(false);
const [sunriseTime, setSunriseTime] = useState(null);
const [sunsetTime, setSunsetTime] = useState(null);
/**
* Save mouse hide state
@@ -307,10 +309,10 @@ export function AppContextProvider({ children }) {
setHourlyWeatherDataErrMsg(null);
const { latitude, longitude } = coords;
const fields = [
"temp",
"precipitation_probability",
"precipitation",
"wind_speed",
"temperature",
"precipitationProbability",
"precipitationIntensity",
"windSpeed",
].join("%2c");
const endTime = new Date(
@@ -330,7 +332,7 @@ export function AppContextProvider({ children }) {
axios
.get(
`https://api.climacell.co/v3/weather/forecast/hourly?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude}&end_time=${endTime}`
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}&timesteps=1h&apikey=${weatherApiKey}&endTime=${endTime}`
)
.then((res) => {
if (!res) {
@@ -365,10 +367,10 @@ export function AppContextProvider({ children }) {
setDailyWeatherDataErrMsg(null);
const { latitude, longitude } = coords;
const fields = [
"temp",
"precipitation_probability",
"precipitation",
"wind_speed",
"temperature",
"precipitationProbability",
"precipitationIntensity",
"windSpeed",
].join("%2c");
const endTime = new Date(
@@ -387,7 +389,7 @@ export function AppContextProvider({ children }) {
}
axios
.get(
`https://api.climacell.co/v3/weather/forecast/daily?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude}&end_time=${endTime}`
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}&timesteps=1d&apikey=${weatherApiKey}&endTime=${endTime}`
)
.then((res) => {
if (!res) {
@@ -407,6 +409,39 @@ export function AppContextProvider({ children }) {
});
}
function updateSunriseSunset(coords) {
return new Promise((resolve, reject) => {
if (!coords) {
setSunriseTime(null);
setSunsetTime(null);
return reject("No coords");
}
const { latitude, longitude } = coords;
axios
.get(
`https://api.sunrise-sunset.org/json?lat=${latitude}&lng=${longitude}&formatted=0`
)
.then((res) => {
const { results } = res?.data;
if (results) {
const { sunrise, sunset } = results;
setSunriseTime(sunrise);
setSunsetTime(sunset);
} else {
setSunriseTime(null);
setSunsetTime(null);
}
resolve(results);
})
.catch((err) => {
setSunriseTime(null);
setSunsetTime(null);
reject(err);
});
});
}
/**
* Updates current weather data
*
@@ -421,17 +456,15 @@ export function AppContextProvider({ children }) {
setCurrentWeatherDataErrMsg(null);
const { latitude, longitude } = coords;
// https://developer.climacell.co/v3/reference#data-layers-weather
const fields = [
"temp",
"temperature",
"humidity",
"wind_speed",
"precipitation",
"precipitation_type",
"sunrise",
"sunset",
"cloud_cover",
"weather_code",
"windSpeed",
"precipitationIntensity",
"precipitationType",
"precipitationProbability",
"cloudCover",
"weatherCode",
].join("%2c");
return new Promise((resolve, reject) => {
if (!coords) {
@@ -443,9 +476,10 @@ export function AppContextProvider({ children }) {
setSettingsMenuOpen(true);
return reject("Missing weather API key");
}
axios
.get(
`https://api.climacell.co/v3/weather/realtime?unit_system=si&fields=${fields}&apikey=${weatherApiKey}&lat=${latitude}&lon=${longitude};`
`https://data.climacell.co/v4/timelines?location=${latitude}%2C${longitude}&fields=${fields}&timesteps=current&apikey=${weatherApiKey}`
)
.then((res) => {
if (!res) {
@@ -572,6 +606,9 @@ export function AppContextProvider({ children }) {
dailyWeatherDataErrMsg,
mouseHide,
saveMouseHide,
updateSunriseSunset,
sunriseTime,
sunsetTime,
};
return (
+54 -50
View File
@@ -4,7 +4,6 @@ import styles from "./styles.css";
import {
convertTemp,
convertSpeed,
convertLength,
} from "~/services/conversions";
import { InlineIcon } from "@iconify/react";
@@ -33,29 +32,29 @@ import daySunnyOvercast from "@iconify/icons-wi/day-sunny-overcast";
* @returns {JSX.Element} Current weather conditions component
*/
const CurrentWeather = () => {
const { currentWeatherData, tempUnit, speedUnit, lengthUnit } = useContext(
const { currentWeatherData, tempUnit, speedUnit, sunriseTime, sunsetTime } = useContext(
AppContext
);
if (currentWeatherData) {
const weatherData =
currentWeatherData?.data?.timelines?.[0]?.intervals[0]?.values;
if (weatherData) {
const {
cloud_cover: { value: cloudCover },
humidity: { value: humidity },
precipitation: { value: precipitation },
precipitation_type: { value: precipitationType },
temp: { value: temp },
weather_code: { value: weatherCode },
wind_speed: { value: windSpeed },
sunrise: { value: sunrise },
sunset: { value: sunset },
} = currentWeatherData;
cloudCover,
humidity,
precipitationType,
precipitationProbability,
temperature,
weatherCode,
windSpeed,
} = weatherData;
const daylight = sunriseTime && sunsetTime ? isDaylight(new Date(sunriseTime), new Date(sunsetTime)) : true;
const { icon: weatherIcon, desc: weatherDesc } =
parseWeatherCode(weatherCode, isDaylight(sunrise, sunset)) || {};
parseWeatherCode(weatherCode, daylight) || {};
return (
<div className={styles.container}>
<div className={styles.currentTemp}>
{convertTemp(temp, tempUnit)}
{convertTemp(temperature, tempUnit)}
<InlineIcon icon={degreesIcon} />
</div>
<div className={styles.iconContainer}>
@@ -65,6 +64,14 @@ const CurrentWeather = () => {
</div>
<div className={styles.stats}>
<div>
<div className={styles.statItem}>
<div>
<InlineIcon
icon={precipitationType === 2 ? snowIcon : rainIcon}
/>
</div>
<div>{precipitationProbability}%</div>
</div>
<div className={styles.statItem}>
<div>
<InlineIcon icon={cloudIcon} />
@@ -88,17 +95,6 @@ const CurrentWeather = () => {
</div>
<div>{parseInt(humidity)}%</div>
</div>
<div className={styles.statItem}>
<div>
<InlineIcon
icon={precipitationType === "snow" ? snowIcon : rainIcon}
/>
</div>
<div className={styles.textUnit}>
<div>{convertLength(precipitation, lengthUnit)}</div>
<div>{lengthUnit}</div>
</div>
</div>
</div>
</div>
<div className={styles.description}>{weatherDesc || ""}</div>
@@ -112,61 +108,69 @@ const CurrentWeather = () => {
/**
* Parse weather code
*
* https://docs.climacell.co/reference/data-layers-overview
*
* @param {String} code
* @param {Boolean} [isDay] if it is currently day
* @returns {Object} weather description and icon
*/
const parseWeatherCode = (code, isDay) => {
switch (code) {
case "freezing_rain_heavy":
case 6201:
return { desc: "Heavy freezing rain", icon: isDay ? dayRain : nightRain };
case "freezing_rain":
case 6001:
return { desc: "Freezing rain", icon: isDay ? dayRain : nightRain };
case "freezing_rain_light":
case 6200:
return { desc: "Light freezing rain", icon: isDay ? dayRain : nightRain };
case "freezing_drizzle":
case 6000:
return { desc: "Freezing drizzle", icon: rainMix };
case "ice_pellets_heavy":
case 7101:
return { desc: "Heavy ice pellets", icon: rainMix };
case "ice_pellets":
case 7000:
return { desc: "Ice pellets", icon: rainMix };
case "ice_pellets_light":
case 7102:
return { desc: "Light ice pellets", icon: rainMix };
case "snow_heavy":
case 5101:
return { desc: "Heavy snow", icon: snowIcon };
case "snow":
case 5000:
return { desc: "Show", icon: snowIcon };
case "snow_light":
case 5100:
return { desc: "Light snow", icon: snowIcon };
case "flurries":
case 5001:
return { desc: "Flurries", icon: snowIcon };
case "tstorm":
case 8000:
return { desc: "Thunder storm", icon: thunderstormIcon };
case "rain_heavy":
case 4201:
return { desc: "Heavy rain", icon: isDay ? dayRain : nightRain };
case "rain":
case 4001:
return { desc: "Rain", icon: isDay ? dayRain : nightRain };
case "rain_light":
case 4200:
return { desc: "Light rain", icon: isDay ? dayRain : nightRain };
case "drizzle":
case 4000:
return { desc: "Drizzle", icon: rainMix };
case "fog_light":
case 2100:
return { desc: "Light fog", icon: fogIcon };
case "fog":
case 2000:
return { desc: "Fog", icon: fogIcon };
case "cloudy":
case 1001:
return { desc: "Cloudy", icon: cloudyIcon };
case "mostly_cloudy":
case 1102:
return { desc: "Mostly cloudy", icon: cloudyIcon };
case "partly_cloudy":
case 1101:
return {
desc: "Partly cloudy",
icon: isDay ? daySunnyOvercast : nightAltCloudy,
};
case "mostly_clear":
case 1100:
return { desc: "Mostly clear", icon: isDay ? dayCloudy : nightAltCloudy };
case "clear":
case 1000:
return { desc: "Clear", icon: isDay ? daySunny : nightClear };
case 3001:
return { desc: "Wind", icon: strongWind };
case 3000:
return { desc: "Light wind", icon: strongWind };
case 3002:
return { desc: "Strong wind", icon: strongWind };
}
};
@@ -16,7 +16,7 @@
}
.icon-container {
grid-area: weather-icon;
grid-area: weather-icon;
display: flex;
justify-content: flex-start;
align-items: center;
+7 -7
View File
@@ -11,12 +11,12 @@ import styles from "./styles.css";
* @returns {JSX.Element} Location name
*/
const LocationName = () => {
const { currentWeatherData, reverseGeoApiKey } = useContext(AppContext);
const { mapGeo, reverseGeoApiKey } = useContext(AppContext);
const [name, setName] = useState(null);
useEffect(() => {
if (currentWeatherData && reverseGeoApiKey) {
const { lat, lon } = currentWeatherData;
if (mapGeo && reverseGeoApiKey) {
const { latitude: lat, longitude: lon } = mapGeo;
reverseGeocode({ lat, lon, apiKey: reverseGeoApiKey })
.then((res) => {
setName(getName(res));
@@ -25,11 +25,11 @@ const LocationName = () => {
setName(`${lat}, ${lon}`);
console.log("err!", err);
});
} else if (currentWeatherData && !reverseGeoApiKey) {
const { lat, lon } = currentWeatherData;
} else if (mapGeo && !reverseGeoApiKey) {
const { latitude: lat, longitude: lon } = mapGeo;
setName(`${lat}, ${lon}`);
}
}, [currentWeatherData, reverseGeoApiKey]);
}, [mapGeo, reverseGeoApiKey]);
return (
<div className={`${styles.container}`}>
@@ -48,7 +48,7 @@ const LocationName = () => {
* @param {Object} res
* @returns {String} Display name
*/
const getName = (res) => {
const getName = (res) => {
// eslint-disable-next-line babel/camelcase
const { city, country, state, country_code, county, region } = res.address;
// eslint-disable-next-line babel/camelcase
+4 -8
View File
@@ -13,24 +13,20 @@ import styles from "./styles.css";
* @returns {JSX.Element} Sunrise / Sunset component
*/
const SunRiseSet = () => {
const { currentWeatherData, clockTime } = useContext(AppContext);
if (currentWeatherData) {
const {
sunrise: { value: sunrise },
sunset: { value: sunset },
} = currentWeatherData;
const { sunriseTime, sunsetTime, clockTime } = useContext(AppContext);
if (sunriseTime && sunsetTime) {
return (
<div className={styles.container}>
<div>
<InlineIcon icon={bxsSun} />
<span>
{format(new Date(sunrise), clockTime === "12" ? "p" : "HH:mm")}
{format(new Date(sunriseTime), clockTime === "12" ? "p" : "HH:mm")}
</span>
</div>
<div>
<InlineIcon icon={bxsMoon} />
<span>
{format(new Date(sunset), clockTime === "12" ? "p" : "HH:mm")}
{format(new Date(sunsetTime), clockTime === "12" ? "p" : "HH:mm")}
</span>
</div>
</div>
+3 -1
View File
@@ -61,6 +61,7 @@ const WeatherInfo = () => {
darkMode,
setSettingsMenuOpen,
currentWeatherData,
updateSunriseSunset
} = useContext(AppContext);
const [
@@ -77,10 +78,11 @@ const WeatherInfo = () => {
const [err, setErr] = useState(null);
const hourlyWeatherUpdateCb = useCallback(() => {
updateSunriseSunset(mapGeo);
updateHourlyWeatherData(mapGeo).catch((err) => {
console.log("err", err);
});
}, [updateHourlyWeatherData, mapGeo]);
}, [updateHourlyWeatherData, updateSunriseSunset, mapGeo]);
const dailyWeatherUpdateCb = useCallback(() => {
updateDailyWeatherData(mapGeo).catch((err) => {
@@ -87,16 +87,22 @@ const createChartOptions = ({
const chartColors = {
blue: "rgba(63, 127, 191, 0.5)",
gray: "rgba(127, 127, 127, 0.5)",
gray2: "rgba(127, 127, 127, 0.3)",
};
const mapChartData = ({ data, tempUnit, speedUnit, altMode, lengthUnit }) => {
const mapChartData = ({
data: weatherData,
tempUnit,
speedUnit,
altMode,
lengthUnit,
}) => {
const data = weatherData?.data?.timelines?.[0]?.intervals;
if (!data) {
return null;
}
return {
labels: data.map((e) => {
const date = new Date(e.observation_time.value);
const date = new Date(e.startTime);
const adjustedTimestamp =
date.getTime() + date.getTimezoneOffset() * 60 * 1000;
return format(new Date(adjustedTimestamp), "EEEEE");
@@ -104,37 +110,30 @@ const mapChartData = ({ data, tempUnit, speedUnit, altMode, lengthUnit }) => {
datasets: [
{
radius: 0,
label: altMode ? "High Wind Speed" : "High Temp",
label: altMode ? "Wind Speed" : "Temp",
data: data.map((e) => {
const {
values: { windSpeed, temperature },
} = e;
return altMode
? convertSpeed(e.wind_speed[1].max.value, speedUnit)
: convertTemp(e.temp[1].max.value, tempUnit);
? convertSpeed(windSpeed, speedUnit)
: convertTemp(temperature, tempUnit);
}),
yAxisID: "y-axis-1",
borderColor: chartColors.gray,
backgroundColor: chartColors.gray,
fill: false,
},
{
radius: 0,
label: altMode ? "Low Wind Speed" : "Low Temp",
data: data.map((e) => {
return altMode
? convertSpeed(e.wind_speed[0].min.value, speedUnit)
: convertTemp(e.temp[0].min.value, tempUnit);
}),
yAxisID: "y-axis-1",
borderColor: chartColors.gray2,
backgroundColor: chartColors.gray2,
fill: false,
},
{
radius: 0,
label: "Precipitation",
data: data.map((e) => {
const {
values: { precipitationIntensity, precipitationProbability },
} = e;
return altMode
? convertLength(e.precipitation[0].max.value, lengthUnit)
: e.precipitation_probability.value;
? convertLength(precipitationIntensity, lengthUnit)
: precipitationProbability;
}),
yAxisID: "y-axis-2",
borderColor: chartColors.blue,
@@ -90,25 +90,26 @@ const chartColors = {
};
const mapChartData = ({
data,
data: weatherData,
tempUnit,
speedUnit,
clockTime,
altMode,
lengthUnit,
}) => {
const data = weatherData?.data?.timelines?.[0]?.intervals;
if (!data) {
return null;
}
return {
labels: data.map((e) => {
if (clockTime === "12") {
return `${format(new Date(e.observation_time.value), "h")}${format(
new Date(e.observation_time.value),
return `${format(new Date(e.startTime), "h")}${format(
new Date(e.startTime),
"aaaaa"
)}`;
} else {
return `${format(new Date(e.observation_time.value), "HH")}`;
return `${format(new Date(e.startTime), "HH")}`;
}
}),
datasets: [
@@ -116,9 +117,12 @@ const mapChartData = ({
radius: 0,
label: altMode ? "Wind Speed" : "Temp",
data: data.map((e) => {
const {
values: { windSpeed, temperature },
} = e;
return altMode
? convertSpeed(e.wind_speed.value, speedUnit)
: convertTemp(e.temp.value, tempUnit);
? convertSpeed(windSpeed, speedUnit)
: convertTemp(temperature, tempUnit);
}),
yAxisID: "y-axis-1",
borderColor: chartColors.gray,
@@ -129,9 +133,12 @@ const mapChartData = ({
radius: 0,
label: "Precipitation",
data: data.map((e) => {
const {
values: { precipitationIntensity, precipitationProbability },
} = e;
return altMode
? convertLength(e.precipitation.value, lengthUnit)
: e.precipitation_probability.value;
? convertLength(precipitationIntensity, lengthUnit)
: precipitationProbability;
}),
yAxisID: "y-axis-2",
borderColor: chartColors.blue,
@@ -196,7 +203,7 @@ const HourlyChart = () => {
/>
</div>
);
}else if (hourlyWeatherDataErr) {
} else if (hourlyWeatherDataErr) {
return (
<div
className={`${darkMode ? styles.dark : styles.light} ${