Skip to content

Commit

Permalink
Merge pull request #4 from TextureHQ/nws/observation-loop
Browse files Browse the repository at this point in the history
feat: improved the nws client algorithm to receive data from differen…
  • Loading branch information
slava-ovchinnikov authored Aug 7, 2024
2 parents 2b2efd7 + d2d5171 commit 942a70f
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "weather-plus",
"version": "0.0.16",
"version": "0.0.17",
"description": "Weather Plus is a powerful wrapper around various Weather APIs that simplifies adding weather data to your application",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
41 changes: 22 additions & 19 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
export interface IWeatherData {
dewPoint: IDewPoint;
humidity: IRelativeHumidity;
temperature: ITemperature;
conditions: IConditions;
export enum IWeatherUnits {
C = 'C',
F = 'F',
percent = 'percent',
string = 'string',
}

export interface ITemperature {
value: number;
unit: 'C' | 'F';
export enum IWeatherKey {
dewPoint = 'dewPoint',
humidity = 'humidity',
temperature = 'temperature',
conditions = 'conditions',
}

export interface IRelativeHumidity {
value: number;
unit: 'percent';
export interface IWeatherData {
[IWeatherKey.dewPoint]: IDewPoint;
[IWeatherKey.humidity]: IRelativeHumidity;
[IWeatherKey.temperature]: ITemperature;
[IWeatherKey.conditions]: IConditions;
}

export interface IDewPoint {
value: number;
unit: 'C' | 'F';
export type IBaseWeatherProperty<T, U extends IWeatherUnits> = {
value: T;
unit: U | keyof typeof IWeatherUnits;
}

export interface IConditions {
value: string;
unit: 'string';
}
export type IRelativeHumidity = IBaseWeatherProperty<number, IWeatherUnits.percent>;
export type IDewPoint = IBaseWeatherProperty<number, IWeatherUnits.C | IWeatherUnits.F>;
export type IConditions = IBaseWeatherProperty<string, IWeatherUnits.string>;
export type ITemperature = IBaseWeatherProperty<number, IWeatherUnits.C | IWeatherUnits.F>;
2 changes: 1 addition & 1 deletion src/providers/nws/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('fetchNearbyStations', () => {
mock.onGet(observationStations).reply(200, mockResponse);

const result = await fetchNearbyStations(observationStations);
expect(result).toBe(mockResponse.features[0].id);
expect(result).toEqual(mockResponse.features);
});
});

Expand Down
60 changes: 48 additions & 12 deletions src/providers/nws/client.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
import axios from 'axios';
import debug from 'debug';
import { IWeatherData } from '../../interfaces';
import { IWeatherData, IWeatherKey, IWeatherUnits } from '../../interfaces';
import {
IGridpointsStations,
IPointsLatLngResponse,
IObservationsLatest,
IFeature,
} from './interfaces';

const log = debug('weather-plus:nws:client');

export const WEATHER_KEYS = Object.values(IWeatherKey);

export async function getWeather(lat: number, lng: number) {
const data: Partial<IWeatherData> = {};
const weatherData: IWeatherData[] = [];

const observationStations = await fetchObservationStationUrl(lat, lng);
const stationId = await fetchNearbyStations(observationStations);
const observation = await fetchLatestObservation(stationId);
return convertToWeatherData(observation);
const stations = await fetchNearbyStations(observationStations);

if (!stations.length) {
throw new Error('No stations found');
}

do {
try {
const stationId = stations.pop()?.id;

if (!stationId) {
break;
}

const observation = await fetchLatestObservation(stationId);
const weather = convertToWeatherData(observation);

weatherData.push(weather);
} catch (error) {
break;
}
}
while (!WEATHER_KEYS.reduce((acc, key) => acc && weatherData.some((data) => data[key]), true) || stations.length > 0);

for (const key of WEATHER_KEYS) {
const value = weatherData.find((data) => data[key]);

if (value && value[key]?.value) {
data[key] = value[key] as never;
}
}

return data;
}

// Fetch the observation station URL from the Weather.gov API
Expand All @@ -32,12 +68,12 @@ export async function fetchObservationStationUrl(
// https://api.weather.gov/gridpoints/OKX/33,35/stations
export async function fetchNearbyStations(
observationStations: string
): Promise<string> {
const stationResponse = await axios.get<IGridpointsStations>(
): Promise<IFeature[]> {
const { data: { features } } = await axios.get<IGridpointsStations>(
observationStations
);
const stationUrl = stationResponse.data.features[0].id;
return stationUrl;

return features;
}

// Fetch the latest observation from the Weather.gov API
Expand All @@ -57,19 +93,19 @@ export function convertToWeatherData(observation: any): IWeatherData {
return {
dewPoint: {
value: properties.dewpoint.value,
unit: properties.dewpoint.unitCode === 'wmoUnit:degC' ? 'C' : 'F',
unit: properties.dewpoint.unitCode === 'wmoUnit:degC' ? IWeatherUnits.C : IWeatherUnits.F,
},
humidity: {
value: properties.relativeHumidity.value,
unit: 'percent',
unit: IWeatherUnits.percent,
},
temperature: {
value: properties.temperature.value,
unit: properties.temperature.unitCode === 'wmoUnit:degC' ? 'C' : 'F',
unit: properties.temperature.unitCode === 'wmoUnit:degC' ? IWeatherUnits.C : IWeatherUnits.F,
},
conditions: {
value: properties.textDescription,
unit: 'string',
unit: IWeatherUnits.string,
}
};
}

0 comments on commit 942a70f

Please sign in to comment.