Skip to content

Commit

Permalink
Now ensuring we are fetching the centerpoint of a geohash rather than…
Browse files Browse the repository at this point in the history
… a random point within it
  • Loading branch information
victorquinn committed Oct 10, 2024
1 parent 8acfbed commit 8399985
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ You can bypass the cache and force a fresh request to the provider by setting th
const weather = await weatherPlus.getWeather(51.5074, -0.1278, { bypassCache: true });
```

This will not entirely bypass the cache, it bypasses it for the read request and then the returned data is cached again for future use.

### Geohash Precision

The library uses geohashing to cache weather data for nearby locations efficiently. Geohashing converts latitude and longitude into a short alphanumeric string, representing an area on the Earth’s surface.
Expand Down
12 changes: 9 additions & 3 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import WeatherPlus from './index';
import axios from 'axios';
import geohash from 'ngeohash';
import MockAdapter from 'axios-mock-adapter';
import { IWeatherData } from './interfaces';
import { InvalidProviderLocationError } from './errors';
Expand Down Expand Up @@ -99,8 +100,9 @@ describe('WeatherPlus Library', () => {
},
];

const { latitude, longitude } = geohash.decode(geohash.encode(lat, lng, 5));
// Mock NWS API responses
mock.onGet(`https://api.weather.gov/points/${lat},${lng}`).reply(200, mockResponses[0]);
mock.onGet(`https://api.weather.gov/points/${latitude},${longitude}`).reply(200, mockResponses[0]);
mock
.onGet('https://api.weather.gov/gridpoints/OKX/33,35/stations')
.reply(200, mockResponses[1]);
Expand Down Expand Up @@ -194,8 +196,10 @@ describe('WeatherPlus Library', () => {
const lat = 51.5074; // London, UK
const lng = -0.1278;

const { latitude, longitude } = geohash.decode(geohash.encode(lat, lng, 5));

// Mock NWS to throw an error
mock.onGet(`https://api.weather.gov/points/${lat},${lng}`).reply(500);
mock.onGet(`https://api.weather.gov/points/${latitude},${longitude}`).reply(500);
// Mock OpenWeather to return 500
mock
.onGet('https://api.openweathermap.org/data/3.0/onecall')
Expand Down Expand Up @@ -259,8 +263,10 @@ describe('WeatherPlus Library', () => {
},
];

const { latitude, longitude } = geohash.decode(geohash.encode(lat, lng, 5));

// Mock NWS API responses
mock.onGet(`https://api.weather.gov/points/${lat},${lng}`).reply(200, mockResponses[0]);
mock.onGet(`https://api.weather.gov/points/${latitude},${longitude}`).reply(200, mockResponses[0]);
mock
.onGet('https://api.weather.gov/gridpoints/OKX/33,35/stations')
.reply(200, mockResponses[1]);
Expand Down
8 changes: 5 additions & 3 deletions src/weatherService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WeatherService, GetWeatherOptions } from './weatherService';
import { InvalidProviderLocationError } from './errors';
import { IWeatherUnits, IWeatherData } from './interfaces';
import { IWeatherProvider } from './providers/IWeatherProvider';
import geohash from 'ngeohash';

jest.mock('./cache', () => {
return {
Expand Down Expand Up @@ -242,7 +243,6 @@ describe('WeatherService', () => {
});

it('should verify that the provider name is included in the weather data', async () => {
// Arrange
const lat = 37.7749; // San Francisco
const lng = -122.4194;

Expand Down Expand Up @@ -271,7 +271,8 @@ describe('WeatherService', () => {
const result = await weatherService.getWeather(lat, lng);

// Assert
expect(mockProvider.getWeather).toHaveBeenCalledWith(lat, lng);
const { latitude, longitude } = geohash.decode(geohash.encode(lat, lng, 5));
expect(mockProvider.getWeather).toHaveBeenCalledWith(latitude, longitude);
expect(result).toEqual(mockWeatherData);
expect(result.provider).toBe('openweather'); // Verify provider name
});
Expand Down Expand Up @@ -304,8 +305,9 @@ describe('WeatherService', () => {
// Expect cache.get not to be called
expect(mockCache.get).not.toHaveBeenCalled();

const { latitude, longitude } = geohash.decode(geohash.encode(0, 0, 5));
// Expect provider.getWeather to be called
expect(mockProvider.getWeather).toHaveBeenCalledWith(0, 0);
expect(mockProvider.getWeather).toHaveBeenCalledWith(latitude, longitude);

// Expect cache.set to be called with new data
expect(mockCache.set).toHaveBeenCalledWith(expect.any(String), JSON.stringify(result));
Expand Down
15 changes: 12 additions & 3 deletions src/weatherService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,25 @@ export class WeatherService {
try {
log(`Trying provider ${provider.name} for (${lat}, ${lng})`);

// Convert geohash to lat/lng using ngeohash
// This ensures that the lat/lng we are pulling weather from is the center of the geohash
// rather than the original lat/lng that was passed in that could be on the edge of the geohash.
const {
latitude: geohashLat,
longitude: geohashLng
} = geohash.decode(locationGeohash);
log(`Using geohash center point: (${geohashLat}, ${geohashLng})`);

// Check if provider supports the given location (e.g., NWS only supports US locations)
if (provider.name === 'nws' && !isLocationInUS(lat, lng)) {
log(`Provider ${provider.name} does not support location (${lat}, ${lng})`);
if (provider.name === 'nws' && !isLocationInUS(geohashLat, geohashLng)) {
log(`Provider ${provider.name} does not support location (${geohashLat}, ${geohashLng})`);
throw new InvalidProviderLocationError(
`${provider.name} provider does not support the provided location.`
);
}

// Attempt to get weather data from the provider
const weather = await provider.getWeather(lat, lng);
const weather = await provider.getWeather(geohashLat, geohashLng);

// Store the retrieved weather data in cache
await this.cache.set(locationGeohash, JSON.stringify(weather));
Expand Down

0 comments on commit 8399985

Please sign in to comment.