diff --git a/README.md b/README.md index 7e52d7b..1d19e5d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/index.test.ts b/src/index.test.ts index ee07a7a..239343e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -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'; @@ -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]); @@ -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') @@ -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]); diff --git a/src/weatherService.test.ts b/src/weatherService.test.ts index ae9e1e6..256e1ea 100644 --- a/src/weatherService.test.ts +++ b/src/weatherService.test.ts @@ -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 { @@ -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; @@ -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 }); @@ -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)); diff --git a/src/weatherService.ts b/src/weatherService.ts index 8042f73..4d4b1a5 100644 --- a/src/weatherService.ts +++ b/src/weatherService.ts @@ -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));