From 2280d6580ad04f0240616503bd85058ef1a55dc6 Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 25 Jan 2024 18:37:01 -0500 Subject: [PATCH 1/3] Set timezone when station is selected. --- src/components/App.tsx | 21 ++++++---- src/components/location-picker.tsx | 65 ++++++++++++++++++++++-------- src/constants.ts | 10 +++++ src/types.ts | 8 +++- src/utils/noaaApiHelper.ts | 22 +++++----- 5 files changed, 89 insertions(+), 37 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 6630242..bf32558 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -52,11 +52,15 @@ export const App = () => { }; const fetchSuccessHandler = async (data: any) => { - const {stationTimezoneOffset, weatherStation, selectedFrequency, startDate, endDate, units} = state; - if (data && weatherStation) { + const {startDate, endDate, units, selectedFrequency, + weatherStation, timezone} = state; + const allDefined = (startDate && endDate && units && selectedFrequency && + weatherStation && timezone); + + if (data && allDefined) { const formatDataProps = { data, - stationTimezoneOffset, + timezone, weatherStation, frequency: selectedFrequency, startDate, @@ -102,9 +106,12 @@ export const App = () => { }; const handleGetData = async () => { - const { location, startDate, endDate, selectedFrequency, weatherStation, stationTimezoneOffset } = state; - const attributes = state.frequencies[selectedFrequency].attrs.map(attr => attr.name); - if (location && attributes && startDate && endDate && weatherStation && selectedFrequency) { + const { location, startDate, endDate, weatherStation, frequencies, + selectedFrequency, timezone } = state; + const attributes = frequencies[selectedFrequency].attrs.map(attr => attr.name); + const allDefined = (startDate && endDate && location && weatherStation && timezone); + + if (allDefined) { const isEndDateAfterStartDate = endDate.getTime() >= startDate.getTime(); if (isEndDateAfterStartDate) { setStatusMessage("Fetching weather records from NOAA"); @@ -114,7 +121,7 @@ export const App = () => { frequency: selectedFrequency, weatherStation, attributes, - stationTimezoneOffset + gmtOffset: timezone.gmtOffset }); try { const tRequest = new Request(tURL); diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index da3b11a..8120b69 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -8,11 +8,13 @@ import OpenMapIcon from "../assets/images/icon-map.svg"; // import EditIcon from "../assets/images/icon-edit.svg"; import LocationIcon from "../assets/images/icon-location.svg"; import CurrentLocationIcon from "../assets/images/icon-current-location.svg"; +import { geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import "./location-picker.scss"; export const LocationPicker = () => { - const {state, setState} = useStateContext(); + const { state, setState } = useStateContext(); + const { location, units, weatherStation, weatherStationDistance } = state; const [showMapButton, setShowMapButton] = useState(false); const [isEditing, setIsEditing] = useState(false); const [locationPossibilities, setLocationPossibilities] = useState([]); @@ -22,12 +24,10 @@ export const LocationPicker = () => { const locationDivRef = useRef(null); const locationInputEl = useRef(null); const locationSelectionListEl = useRef(null); - const selectedLocation = state.location; - const unit = state.units; - const unitDistanceText = unit === "standard" ? "mi" : "km"; - const stationDistance = state.weatherStationDistance && unit === "standard" - ? Math.round((state.weatherStationDistance * 0.6 * 10) / 10) - :state.weatherStationDistance && Math.round(state.weatherStationDistance * 10) / 10; + const unitDistanceText = units === "standard" ? "mi" : "km"; + const stationDistance = weatherStationDistance && units === "standard" + ? Math.round((weatherStationDistance * 0.6 * 10) / 10) + : weatherStationDistance && Math.round(weatherStationDistance * 10) / 10; const handleOpenMap = () => { //send request to CODAP to open map with available weather stations @@ -36,7 +36,6 @@ export const LocationPicker = () => { useEffect(() => { if (locationInputEl.current?.value === "") { setShowSelectionList(false); - // setSelectedLocation(undefined); } }, [locationInputEl.current?.value]); @@ -47,8 +46,8 @@ export const LocationPicker = () => { }, [isEditing]); useEffect(() => { - if (selectedLocation) { - findNearestActiveStation(selectedLocation.latitude, selectedLocation.longitude, 80926000, "present") + if (location) { + findNearestActiveStation(location.latitude, location.longitude, 80926000, "present") .then(({station, distance}) => { if (station) { setState((draft) => { @@ -59,7 +58,39 @@ export const LocationPicker = () => { }); } // eslint-disable-next-line react-hooks/exhaustive-deps - },[selectedLocation]); + }, [location]); + + useEffect(() => { + if (weatherStation) { + const fetchTimezone = async (lat: number, long: number) => { + let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; + let res = await fetch(url); + if (res) { + if (res.ok) { + const timezoneData = await res.json(); + const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; + setState((draft) => { + draft.timezone = { + gmtOffset, + name: kOffsetMap[gmtOffset] + }; + }); + } else { + console.warn(res.statusText); + } + } else { + console.warn(`Failed to fetch timezone data for station ${weatherStation.name}`); + } + }; + + fetchTimezone(weatherStation.latitude, weatherStation.longitude); + } else { + setState((draft) => { + draft.timezone = undefined; + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [weatherStation]); const getLocationList = () => { if (locationInputEl.current) { @@ -182,13 +213,13 @@ export const LocationPicker = () => {
Location - { selectedLocation && !isEditing && + { location && !isEditing &&
- { state.weatherStation && + { weatherStation && <> - {state.weatherStationDistance && + {weatherStationDistance && ({stationDistance} {unitDistanceText}) } - {state.weatherStation?.name} + {weatherStation?.name} {/* hide this for now until implemented*/} } @@ -200,10 +231,10 @@ export const LocationPicker = () => {
- { selectedLocation && !isEditing + { location && !isEditing ?
Stations near - {state.location?.name} + {location?.name}
: diff --git a/src/constants.ts b/src/constants.ts index 72cee98..b833808 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -145,3 +145,13 @@ export const kWeatherStationCollectionAttrs = [ }, } ]; + +export const kOffsetMap = { + "-4": "AST", + "-5": "EST", + "-6": "CST", + "-7": "MST", + "-8": "PST", + "-9": "AKST", + "-10": "HST" +}; diff --git a/src/types.ts b/src/types.ts index 91507f4..8f3b598 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,6 +100,11 @@ interface IWeatherStationID { id: string; } +export interface ITimeZone { + gmtOffset: string; + name: string; +} + export interface IState { location?: IPlace; weatherStation?: IWeatherStation; @@ -112,8 +117,7 @@ export interface IState { endDate?: Date; units: IUnits; showModal?: "info" | "data-return-warning"; - stationTimezoneOffset?: number; - stationTimezoneName?: string; + timezone?: ITimeZone; didUserSelectDate: boolean; } diff --git a/src/utils/noaaApiHelper.ts b/src/utils/noaaApiHelper.ts index b0af7c4..1d5aa59 100644 --- a/src/utils/noaaApiHelper.ts +++ b/src/utils/noaaApiHelper.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { IFrequency, IRecord, IUnits, IWeatherStation } from "../types"; +import { IFrequency, IRecord, ITimeZone, IUnits, IWeatherStation } from "../types"; import { frequencyToReportTypeMap, nceiBaseURL } from "../constants"; import { dataTypeStore } from "./noaaDataTypes"; @@ -19,23 +19,22 @@ export const convertUnits = (fromUnitSystem: IUnits, toUnitSystem: IUnits, data: interface IFormatData { data: IRecord[]; - stationTimezoneOffset?: number; - stationTimezoneName?: string; units: IUnits; frequency: IFrequency; weatherStation: IWeatherStation; + timezone: ITimeZone; } export const formatData = (props: IFormatData) => { - const {data, stationTimezoneOffset, stationTimezoneName, units, frequency, weatherStation} = props; + const {data, timezone, units, frequency, weatherStation} = props; const database = frequencyToReportTypeMap[frequency]; let dataRecords: any[] = []; data.forEach((r: any) => { const aValue = convertNOAARecordToValue(r, weatherStation, database); aValue.latitude = weatherStation.latitude; aValue.longitude = weatherStation.longitude; - aValue["UTC offset"] = stationTimezoneOffset || ""; - aValue.timezone = stationTimezoneName || ""; + aValue["UTC offset"] = timezone.gmtOffset; + aValue.timezone = timezone.name; aValue.elevation = weatherStation.elevation; aValue["report type"] = frequency; dataRecords.push(aValue); @@ -84,19 +83,20 @@ interface IComposeURL { frequency: IFrequency; attributes: string[]; weatherStation: IWeatherStation; - stationTimezoneOffset?: number; + gmtOffset: string; } export const composeURL = (props: IComposeURL) => { - const { startDate, endDate, frequency, attributes, weatherStation, stationTimezoneOffset } = props; + const { startDate, endDate, frequency, attributes, weatherStation, gmtOffset } = props; const database = frequencyToReportTypeMap[frequency]; const format = "YYYY-MM-DDThh:mm:ss"; let sDate = dayjs(startDate); let eDate = dayjs(endDate); + // adjust for local station time - if (database === "global-hourly" && stationTimezoneOffset) { - sDate = dayjs(startDate).subtract(stationTimezoneOffset, "hour"); - eDate = dayjs(endDate).subtract(stationTimezoneOffset, "hour").add(1, "day"); + if (database === "global-hourly") { + sDate = dayjs(startDate).subtract(Number(gmtOffset), "hour"); + eDate = dayjs(endDate).subtract(Number(gmtOffset), "hour").add(1, "day"); } const startDateString = dayjs(sDate).format(format); const endDateString = dayjs(eDate).format(format); From 9e3004b188a0dd8ed94c19c32f7b354bada97b25 Mon Sep 17 00:00:00 2001 From: lublagg Date: Thu, 25 Jan 2024 18:43:48 -0500 Subject: [PATCH 2/3] Change dependency to location instead of weatherStation. --- src/components/location-picker.tsx | 58 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index 8120b69..0fba750 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -54,44 +54,38 @@ export const LocationPicker = () => { draft.weatherStation = station; draft.weatherStationDistance = distance; }); + + const fetchTimezone = async (lat: number, long: number) => { + let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; + let res = await fetch(url); + if (res) { + if (res.ok) { + const timezoneData = await res.json(); + const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; + setState((draft) => { + draft.timezone = { + gmtOffset, + name: kOffsetMap[gmtOffset] + }; + }); + } else { + console.warn(res.statusText); + } + } else { + console.warn(`Failed to fetch timezone data for ${location}`); + } + }; + fetchTimezone(location.latitude, location.longitude); } }); + } else { + setState((draft) => { + draft.timezone = undefined; + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [location]); - useEffect(() => { - if (weatherStation) { - const fetchTimezone = async (lat: number, long: number) => { - let url = `${timezoneServiceURL}?lat=${lat}&lng=${long}&username=${geonamesUser}`; - let res = await fetch(url); - if (res) { - if (res.ok) { - const timezoneData = await res.json(); - const { gmtOffset } = timezoneData as { gmtOffset: keyof typeof kOffsetMap }; - setState((draft) => { - draft.timezone = { - gmtOffset, - name: kOffsetMap[gmtOffset] - }; - }); - } else { - console.warn(res.statusText); - } - } else { - console.warn(`Failed to fetch timezone data for station ${weatherStation.name}`); - } - }; - - fetchTimezone(weatherStation.latitude, weatherStation.longitude); - } else { - setState((draft) => { - draft.timezone = undefined; - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [weatherStation]); - const getLocationList = () => { if (locationInputEl.current) { autoComplete(locationInputEl.current) From 9e97bcaf83e294d99be552a4db91d7138ec501e1 Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 25 Jan 2024 17:02:42 -0800 Subject: [PATCH 3/3] Fix build errors --- src/components/location-picker.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index 3088368..1ed1a0f 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import classnames from "classnames"; import { createMap, selectStations } from "../utils/codapHelpers"; import { autoComplete, geoLocSearch } from "../utils/geonameSearch"; -import { kStationsCollectionName } from "../constants"; +import { kStationsCollectionName, geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import { useStateContext } from "../hooks/use-state"; import { IPlace } from "../types"; import { findNearestActiveStation } from "../utils/getWeatherStations"; @@ -10,7 +10,6 @@ import OpenMapIcon from "../assets/images/icon-map.svg"; // import EditIcon from "../assets/images/icon-edit.svg"; import LocationIcon from "../assets/images/icon-location.svg"; import CurrentLocationIcon from "../assets/images/icon-current-location.svg"; -import { geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import "./location-picker.scss"; @@ -26,7 +25,6 @@ export const LocationPicker = () => { const locationDivRef = useRef(null); const locationInputEl = useRef(null); const locationSelectionListEl = useRef(null); - const selectedLocation = location; const unitDistanceText = units === "standard" ? "mi" : "km"; const stationDistance = weatherStationDistance && units === "standard" ? Math.round((weatherStationDistance * 0.6 * 10) / 10)