@jablum/weather-mcp 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1319 -0
- package/dist/analytics/anonymizer.d.ts +37 -0
- package/dist/analytics/anonymizer.d.ts.map +1 -0
- package/dist/analytics/anonymizer.js +112 -0
- package/dist/analytics/anonymizer.js.map +1 -0
- package/dist/analytics/collector.d.ts +72 -0
- package/dist/analytics/collector.d.ts.map +1 -0
- package/dist/analytics/collector.js +282 -0
- package/dist/analytics/collector.js.map +1 -0
- package/dist/analytics/config.d.ts +15 -0
- package/dist/analytics/config.d.ts.map +1 -0
- package/dist/analytics/config.js +172 -0
- package/dist/analytics/config.js.map +1 -0
- package/dist/analytics/index.d.ts +8 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +7 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/middleware.d.ts +33 -0
- package/dist/analytics/middleware.d.ts.map +1 -0
- package/dist/analytics/middleware.js +99 -0
- package/dist/analytics/middleware.js.map +1 -0
- package/dist/analytics/transport.d.ts +11 -0
- package/dist/analytics/transport.d.ts.map +1 -0
- package/dist/analytics/transport.js +92 -0
- package/dist/analytics/transport.js.map +1 -0
- package/dist/analytics/types.d.ts +74 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +6 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/config/api.d.ts +30 -0
- package/dist/config/api.d.ts.map +1 -0
- package/dist/config/api.js +32 -0
- package/dist/config/api.js.map +1 -0
- package/dist/config/cache.d.ts +31 -0
- package/dist/config/cache.d.ts.map +1 -0
- package/dist/config/cache.js +108 -0
- package/dist/config/cache.js.map +1 -0
- package/dist/config/displayThresholds.d.ts +83 -0
- package/dist/config/displayThresholds.d.ts.map +1 -0
- package/dist/config/displayThresholds.js +83 -0
- package/dist/config/displayThresholds.js.map +1 -0
- package/dist/config/tools.d.ts +44 -0
- package/dist/config/tools.d.ts.map +1 -0
- package/dist/config/tools.js +269 -0
- package/dist/config/tools.js.map +1 -0
- package/dist/errors/ApiError.d.ts +62 -0
- package/dist/errors/ApiError.d.ts.map +1 -0
- package/dist/errors/ApiError.js +171 -0
- package/dist/errors/ApiError.js.map +1 -0
- package/dist/handlers/airQualityHandler.d.ts +11 -0
- package/dist/handlers/airQualityHandler.d.ts.map +1 -0
- package/dist/handlers/airQualityHandler.js +154 -0
- package/dist/handlers/airQualityHandler.js.map +1 -0
- package/dist/handlers/alertsHandler.d.ts +11 -0
- package/dist/handlers/alertsHandler.d.ts.map +1 -0
- package/dist/handlers/alertsHandler.js +98 -0
- package/dist/handlers/alertsHandler.js.map +1 -0
- package/dist/handlers/currentConditionsHandler.d.ts +13 -0
- package/dist/handlers/currentConditionsHandler.d.ts.map +1 -0
- package/dist/handlers/currentConditionsHandler.js +296 -0
- package/dist/handlers/currentConditionsHandler.js.map +1 -0
- package/dist/handlers/forecastHandler.d.ts +16 -0
- package/dist/handlers/forecastHandler.d.ts.map +1 -0
- package/dist/handlers/forecastHandler.js +454 -0
- package/dist/handlers/forecastHandler.js.map +1 -0
- package/dist/handlers/historicalWeatherHandler.d.ts +12 -0
- package/dist/handlers/historicalWeatherHandler.d.ts.map +1 -0
- package/dist/handlers/historicalWeatherHandler.js +188 -0
- package/dist/handlers/historicalWeatherHandler.js.map +1 -0
- package/dist/handlers/lightningHandler.d.ts +14 -0
- package/dist/handlers/lightningHandler.d.ts.map +1 -0
- package/dist/handlers/lightningHandler.js +258 -0
- package/dist/handlers/lightningHandler.js.map +1 -0
- package/dist/handlers/locationHandler.d.ts +12 -0
- package/dist/handlers/locationHandler.d.ts.map +1 -0
- package/dist/handlers/locationHandler.js +149 -0
- package/dist/handlers/locationHandler.js.map +1 -0
- package/dist/handlers/marineConditionsHandler.d.ts +13 -0
- package/dist/handlers/marineConditionsHandler.d.ts.map +1 -0
- package/dist/handlers/marineConditionsHandler.js +270 -0
- package/dist/handlers/marineConditionsHandler.js.map +1 -0
- package/dist/handlers/riverConditionsHandler.d.ts +11 -0
- package/dist/handlers/riverConditionsHandler.d.ts.map +1 -0
- package/dist/handlers/riverConditionsHandler.js +176 -0
- package/dist/handlers/riverConditionsHandler.js.map +1 -0
- package/dist/handlers/savedLocationsHandler.d.ts +50 -0
- package/dist/handlers/savedLocationsHandler.d.ts.map +1 -0
- package/dist/handlers/savedLocationsHandler.js +397 -0
- package/dist/handlers/savedLocationsHandler.js.map +1 -0
- package/dist/handlers/statusHandler.d.ts +12 -0
- package/dist/handlers/statusHandler.d.ts.map +1 -0
- package/dist/handlers/statusHandler.js +115 -0
- package/dist/handlers/statusHandler.js.map +1 -0
- package/dist/handlers/weatherImageryHandler.d.ts +14 -0
- package/dist/handlers/weatherImageryHandler.d.ts.map +1 -0
- package/dist/handlers/weatherImageryHandler.js +143 -0
- package/dist/handlers/weatherImageryHandler.js.map +1 -0
- package/dist/handlers/wildfireHandler.d.ts +11 -0
- package/dist/handlers/wildfireHandler.d.ts.map +1 -0
- package/dist/handlers/wildfireHandler.js +186 -0
- package/dist/handlers/wildfireHandler.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +735 -0
- package/dist/index.js.map +1 -0
- package/dist/services/blitzortung.d.ts +67 -0
- package/dist/services/blitzortung.d.ts.map +1 -0
- package/dist/services/blitzortung.js +475 -0
- package/dist/services/blitzortung.js.map +1 -0
- package/dist/services/geocoding.d.ts +57 -0
- package/dist/services/geocoding.d.ts.map +1 -0
- package/dist/services/geocoding.js +393 -0
- package/dist/services/geocoding.js.map +1 -0
- package/dist/services/locationStore.d.ts +62 -0
- package/dist/services/locationStore.d.ts.map +1 -0
- package/dist/services/locationStore.js +201 -0
- package/dist/services/locationStore.js.map +1 -0
- package/dist/services/ncei.d.ts +61 -0
- package/dist/services/ncei.d.ts.map +1 -0
- package/dist/services/ncei.js +126 -0
- package/dist/services/ncei.js.map +1 -0
- package/dist/services/nifc.d.ts +44 -0
- package/dist/services/nifc.d.ts.map +1 -0
- package/dist/services/nifc.js +159 -0
- package/dist/services/nifc.js.map +1 -0
- package/dist/services/noaa.d.ts +161 -0
- package/dist/services/noaa.d.ts.map +1 -0
- package/dist/services/noaa.js +681 -0
- package/dist/services/noaa.js.map +1 -0
- package/dist/services/nominatim.d.ts +62 -0
- package/dist/services/nominatim.d.ts.map +1 -0
- package/dist/services/nominatim.js +254 -0
- package/dist/services/nominatim.js.map +1 -0
- package/dist/services/openmeteo.d.ts +189 -0
- package/dist/services/openmeteo.d.ts.map +1 -0
- package/dist/services/openmeteo.js +936 -0
- package/dist/services/openmeteo.js.map +1 -0
- package/dist/services/rainviewer.d.ts +37 -0
- package/dist/services/rainviewer.d.ts.map +1 -0
- package/dist/services/rainviewer.js +115 -0
- package/dist/services/rainviewer.js.map +1 -0
- package/dist/types/imagery.d.ts +82 -0
- package/dist/types/imagery.d.ts.map +1 -0
- package/dist/types/imagery.js +6 -0
- package/dist/types/imagery.js.map +1 -0
- package/dist/types/lightning.d.ts +89 -0
- package/dist/types/lightning.d.ts.map +1 -0
- package/dist/types/lightning.js +6 -0
- package/dist/types/lightning.js.map +1 -0
- package/dist/types/noaa.d.ts +535 -0
- package/dist/types/noaa.d.ts.map +1 -0
- package/dist/types/noaa.js +5 -0
- package/dist/types/noaa.js.map +1 -0
- package/dist/types/nominatim.d.ts +72 -0
- package/dist/types/nominatim.d.ts.map +1 -0
- package/dist/types/nominatim.js +6 -0
- package/dist/types/nominatim.js.map +1 -0
- package/dist/types/openmeteo.d.ts +583 -0
- package/dist/types/openmeteo.d.ts.map +1 -0
- package/dist/types/openmeteo.js +6 -0
- package/dist/types/openmeteo.js.map +1 -0
- package/dist/types/savedLocations.d.ts +58 -0
- package/dist/types/savedLocations.d.ts.map +1 -0
- package/dist/types/savedLocations.js +5 -0
- package/dist/types/savedLocations.js.map +1 -0
- package/dist/types/wildfire.d.ts +83 -0
- package/dist/types/wildfire.d.ts.map +1 -0
- package/dist/types/wildfire.js +5 -0
- package/dist/types/wildfire.js.map +1 -0
- package/dist/utils/airQuality.d.ts +54 -0
- package/dist/utils/airQuality.d.ts.map +1 -0
- package/dist/utils/airQuality.js +251 -0
- package/dist/utils/airQuality.js.map +1 -0
- package/dist/utils/cache.d.ts +69 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +164 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/distance.d.ts +25 -0
- package/dist/utils/distance.d.ts.map +1 -0
- package/dist/utils/distance.js +40 -0
- package/dist/utils/distance.js.map +1 -0
- package/dist/utils/fireWeather.d.ts +76 -0
- package/dist/utils/fireWeather.d.ts.map +1 -0
- package/dist/utils/fireWeather.js +243 -0
- package/dist/utils/fireWeather.js.map +1 -0
- package/dist/utils/geography.d.ts +79 -0
- package/dist/utils/geography.d.ts.map +1 -0
- package/dist/utils/geography.js +266 -0
- package/dist/utils/geography.js.map +1 -0
- package/dist/utils/geohash.d.ts +62 -0
- package/dist/utils/geohash.d.ts.map +1 -0
- package/dist/utils/geohash.js +146 -0
- package/dist/utils/geohash.js.map +1 -0
- package/dist/utils/locationResolver.d.ts +34 -0
- package/dist/utils/locationResolver.d.ts.map +1 -0
- package/dist/utils/locationResolver.js +120 -0
- package/dist/utils/locationResolver.js.map +1 -0
- package/dist/utils/logger.d.ts +75 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +153 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/marine.d.ts +59 -0
- package/dist/utils/marine.d.ts.map +1 -0
- package/dist/utils/marine.js +215 -0
- package/dist/utils/marine.js.map +1 -0
- package/dist/utils/normals.d.ts +86 -0
- package/dist/utils/normals.d.ts.map +1 -0
- package/dist/utils/normals.js +223 -0
- package/dist/utils/normals.js.map +1 -0
- package/dist/utils/snow.d.ts +45 -0
- package/dist/utils/snow.d.ts.map +1 -0
- package/dist/utils/snow.js +144 -0
- package/dist/utils/snow.js.map +1 -0
- package/dist/utils/temperatureConversion.d.ts +12 -0
- package/dist/utils/temperatureConversion.d.ts.map +1 -0
- package/dist/utils/temperatureConversion.js +17 -0
- package/dist/utils/temperatureConversion.js.map +1 -0
- package/dist/utils/timezone.d.ts +56 -0
- package/dist/utils/timezone.d.ts.map +1 -0
- package/dist/utils/timezone.js +167 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/utils/units.d.ts +69 -0
- package/dist/utils/units.d.ts.map +1 -0
- package/dist/utils/units.js +158 -0
- package/dist/utils/units.js.map +1 -0
- package/dist/utils/validation.d.ts +89 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +177 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/version.d.ts +15 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +24 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for managing saved/favorite locations
|
|
3
|
+
* Stores locations in ~/.weather-mcp/locations.json
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { validateLatitude, validateLongitude } from '../utils/validation.js';
|
|
10
|
+
export class LocationStore {
|
|
11
|
+
storePath;
|
|
12
|
+
storeDir;
|
|
13
|
+
cache = null;
|
|
14
|
+
constructor(customPath) {
|
|
15
|
+
if (customPath) {
|
|
16
|
+
this.storePath = customPath;
|
|
17
|
+
this.storeDir = join(customPath, '..');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.storeDir = join(homedir(), '.weather-mcp');
|
|
21
|
+
this.storePath = join(this.storeDir, 'locations.json');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure the storage directory exists
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
ensureDirectoryExists() {
|
|
29
|
+
if (!existsSync(this.storeDir)) {
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(this.storeDir, { recursive: true });
|
|
32
|
+
logger.info('Created locations storage directory', { path: this.storeDir });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.error('Failed to create storage directory', error, {
|
|
36
|
+
path: this.storeDir
|
|
37
|
+
});
|
|
38
|
+
throw new Error(`Failed to create storage directory at ${this.storeDir}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load all saved locations from disk
|
|
44
|
+
*/
|
|
45
|
+
load() {
|
|
46
|
+
// Return cached data if available
|
|
47
|
+
if (this.cache !== null) {
|
|
48
|
+
return this.cache;
|
|
49
|
+
}
|
|
50
|
+
// If file doesn't exist, return empty store
|
|
51
|
+
if (!existsSync(this.storePath)) {
|
|
52
|
+
logger.info('No saved locations file found, starting fresh', {
|
|
53
|
+
path: this.storePath
|
|
54
|
+
});
|
|
55
|
+
this.cache = {};
|
|
56
|
+
return this.cache;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const data = readFileSync(this.storePath, 'utf-8');
|
|
60
|
+
const parsed = JSON.parse(data);
|
|
61
|
+
// Validate the structure
|
|
62
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
63
|
+
logger.warn('Invalid locations file format, resetting to empty', {
|
|
64
|
+
path: this.storePath
|
|
65
|
+
});
|
|
66
|
+
this.cache = {};
|
|
67
|
+
return this.cache;
|
|
68
|
+
}
|
|
69
|
+
logger.info('Loaded saved locations', {
|
|
70
|
+
count: Object.keys(parsed).length,
|
|
71
|
+
path: this.storePath
|
|
72
|
+
});
|
|
73
|
+
this.cache = parsed;
|
|
74
|
+
return this.cache;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error('Failed to load saved locations', error, {
|
|
78
|
+
path: this.storePath
|
|
79
|
+
});
|
|
80
|
+
// Return empty store on error rather than failing
|
|
81
|
+
this.cache = {};
|
|
82
|
+
return this.cache;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Save all locations to disk
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
save(locations) {
|
|
90
|
+
this.ensureDirectoryExists();
|
|
91
|
+
try {
|
|
92
|
+
const data = JSON.stringify(locations, null, 2);
|
|
93
|
+
writeFileSync(this.storePath, data, 'utf-8');
|
|
94
|
+
this.cache = locations; // Update cache
|
|
95
|
+
logger.info('Saved locations to disk', {
|
|
96
|
+
count: Object.keys(locations).length,
|
|
97
|
+
path: this.storePath
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
logger.error('Failed to save locations', error, {
|
|
102
|
+
path: this.storePath
|
|
103
|
+
});
|
|
104
|
+
throw new Error(`Failed to save locations to ${this.storePath}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a saved location by alias
|
|
109
|
+
*/
|
|
110
|
+
get(alias) {
|
|
111
|
+
const locations = this.load();
|
|
112
|
+
const normalized = alias.toLowerCase().trim();
|
|
113
|
+
return locations[normalized];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get all saved locations
|
|
117
|
+
*/
|
|
118
|
+
getAll() {
|
|
119
|
+
return this.load();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Save or update a location
|
|
123
|
+
*/
|
|
124
|
+
set(alias, location) {
|
|
125
|
+
// Validate alias
|
|
126
|
+
const normalized = alias.toLowerCase().trim();
|
|
127
|
+
if (!normalized || normalized.length === 0) {
|
|
128
|
+
throw new Error('Location alias cannot be empty');
|
|
129
|
+
}
|
|
130
|
+
if (normalized.length > 50) {
|
|
131
|
+
throw new Error('Location alias must be 50 characters or less');
|
|
132
|
+
}
|
|
133
|
+
// Validate coordinates
|
|
134
|
+
validateLatitude(location.latitude);
|
|
135
|
+
validateLongitude(location.longitude);
|
|
136
|
+
const locations = this.load();
|
|
137
|
+
const isUpdate = normalized in locations;
|
|
138
|
+
const now = new Date().toISOString();
|
|
139
|
+
const savedLocation = {
|
|
140
|
+
...location,
|
|
141
|
+
saved_at: isUpdate ? locations[normalized].saved_at : now,
|
|
142
|
+
updated_at: now
|
|
143
|
+
};
|
|
144
|
+
locations[normalized] = savedLocation;
|
|
145
|
+
this.save(locations);
|
|
146
|
+
logger.info(isUpdate ? 'Updated saved location' : 'Created new saved location', {
|
|
147
|
+
alias: normalized,
|
|
148
|
+
name: location.name
|
|
149
|
+
});
|
|
150
|
+
return savedLocation;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Remove a saved location
|
|
154
|
+
*/
|
|
155
|
+
remove(alias) {
|
|
156
|
+
const locations = this.load();
|
|
157
|
+
const normalized = alias.toLowerCase().trim();
|
|
158
|
+
if (!(normalized in locations)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
delete locations[normalized];
|
|
162
|
+
this.save(locations);
|
|
163
|
+
logger.info('Removed saved location', { alias: normalized });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if a location exists
|
|
168
|
+
*/
|
|
169
|
+
has(alias) {
|
|
170
|
+
const locations = this.load();
|
|
171
|
+
const normalized = alias.toLowerCase().trim();
|
|
172
|
+
return normalized in locations;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the number of saved locations
|
|
176
|
+
*/
|
|
177
|
+
count() {
|
|
178
|
+
const locations = this.load();
|
|
179
|
+
return Object.keys(locations).length;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Clear all saved locations
|
|
183
|
+
*/
|
|
184
|
+
clear() {
|
|
185
|
+
this.save({});
|
|
186
|
+
logger.info('Cleared all saved locations');
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get the storage file path
|
|
190
|
+
*/
|
|
191
|
+
getStorePath() {
|
|
192
|
+
return this.storePath;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Invalidate the cache, forcing reload on next access
|
|
196
|
+
*/
|
|
197
|
+
invalidateCache() {
|
|
198
|
+
this.cache = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=locationStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locationStore.js","sourceRoot":"","sources":["../../src/services/locationStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for interacting with NOAA NCEI (National Centers for Environmental Information)
|
|
3
|
+
* Climate Data Online (CDO) API
|
|
4
|
+
*
|
|
5
|
+
* Documentation: https://www.ncei.noaa.gov/support/access-data-service-api-user-documentation
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This is a simplified implementation. The NCEI API is station-based (not coordinate-based),
|
|
8
|
+
* which adds complexity:
|
|
9
|
+
* 1. Must find nearby weather stations from coordinates
|
|
10
|
+
* 2. Stations may not have all climate normals data
|
|
11
|
+
* 3. Need to handle multiple stations and aggregate data
|
|
12
|
+
*
|
|
13
|
+
* For v1.2.0, we implement a basic structure that falls back to Open-Meteo.
|
|
14
|
+
* Future enhancement: Full NCEI climate normals integration.
|
|
15
|
+
*/
|
|
16
|
+
import type { ClimateNormals } from '../types/openmeteo.js';
|
|
17
|
+
export interface NCEIServiceConfig {
|
|
18
|
+
baseURL?: string;
|
|
19
|
+
timeout?: number;
|
|
20
|
+
token?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* NCEI Climate Data Online (CDO) Service
|
|
24
|
+
*
|
|
25
|
+
* Provides access to official NOAA climate normals for US locations.
|
|
26
|
+
* Requires API token (free from https://www.ncdc.noaa.gov/cdo-web/token)
|
|
27
|
+
*/
|
|
28
|
+
export declare class NCEIService {
|
|
29
|
+
private client;
|
|
30
|
+
private token;
|
|
31
|
+
constructor(config?: NCEIServiceConfig);
|
|
32
|
+
/**
|
|
33
|
+
* Check if NCEI service is available (token configured)
|
|
34
|
+
*/
|
|
35
|
+
isAvailable(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get climate normals for a US location
|
|
38
|
+
*
|
|
39
|
+
* NOTE: This is a placeholder implementation. The NCEI API requires:
|
|
40
|
+
* 1. Station lookup from coordinates
|
|
41
|
+
* 2. Querying climate normals datasets by station
|
|
42
|
+
* 3. Data transformation to our format
|
|
43
|
+
*
|
|
44
|
+
* For v1.2.0, this throws DataNotFoundError to trigger fallback to Open-Meteo.
|
|
45
|
+
* Future enhancement: Implement full NCEI normals retrieval.
|
|
46
|
+
*
|
|
47
|
+
* @param latitude - Latitude (US only)
|
|
48
|
+
* @param longitude - Longitude (US only)
|
|
49
|
+
* @param month - Month (1-12)
|
|
50
|
+
* @param day - Day of month (1-31)
|
|
51
|
+
* @returns Climate normals
|
|
52
|
+
* @throws {DataNotFoundError} - NCEI integration not yet complete
|
|
53
|
+
*/
|
|
54
|
+
getClimateNormals(latitude: number, longitude: number, month: number, day: number): Promise<ClimateNormals>;
|
|
55
|
+
/**
|
|
56
|
+
* Handle API errors
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
private handleError;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=ncei.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ncei.d.ts","sourceRoot":"","sources":["../../src/services/ncei.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAqB;gBAEtB,MAAM,GAAE,iBAAsB;IA8B1C;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;;;;;;;;;;;;;;;;OAiBG;IACG,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC;IAiC1B;;;OAGG;YACW,WAAW;CAyC1B"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for interacting with NOAA NCEI (National Centers for Environmental Information)
|
|
3
|
+
* Climate Data Online (CDO) API
|
|
4
|
+
*
|
|
5
|
+
* Documentation: https://www.ncei.noaa.gov/support/access-data-service-api-user-documentation
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This is a simplified implementation. The NCEI API is station-based (not coordinate-based),
|
|
8
|
+
* which adds complexity:
|
|
9
|
+
* 1. Must find nearby weather stations from coordinates
|
|
10
|
+
* 2. Stations may not have all climate normals data
|
|
11
|
+
* 3. Need to handle multiple stations and aggregate data
|
|
12
|
+
*
|
|
13
|
+
* For v1.2.0, we implement a basic structure that falls back to Open-Meteo.
|
|
14
|
+
* Future enhancement: Full NCEI climate normals integration.
|
|
15
|
+
*/
|
|
16
|
+
import axios from 'axios';
|
|
17
|
+
import { NCEI_API_TOKEN } from '../config/api.js';
|
|
18
|
+
import { logger } from '../utils/logger.js';
|
|
19
|
+
import { DataNotFoundError, RateLimitError, ServiceUnavailableError } from '../errors/ApiError.js';
|
|
20
|
+
import { getUserAgent } from '../utils/version.js';
|
|
21
|
+
/**
|
|
22
|
+
* NCEI Climate Data Online (CDO) Service
|
|
23
|
+
*
|
|
24
|
+
* Provides access to official NOAA climate normals for US locations.
|
|
25
|
+
* Requires API token (free from https://www.ncdc.noaa.gov/cdo-web/token)
|
|
26
|
+
*/
|
|
27
|
+
export class NCEIService {
|
|
28
|
+
client;
|
|
29
|
+
token;
|
|
30
|
+
constructor(config = {}) {
|
|
31
|
+
const { baseURL = 'https://www.ncei.noaa.gov/cdo-web/api/v2', timeout = 30000, token = NCEI_API_TOKEN } = config;
|
|
32
|
+
this.token = token;
|
|
33
|
+
this.client = axios.create({
|
|
34
|
+
baseURL,
|
|
35
|
+
timeout,
|
|
36
|
+
headers: {
|
|
37
|
+
'Accept': 'application/json',
|
|
38
|
+
'User-Agent': getUserAgent()
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// Add token to requests if available
|
|
42
|
+
if (this.token) {
|
|
43
|
+
this.client.defaults.headers.common['token'] = this.token;
|
|
44
|
+
}
|
|
45
|
+
// Add response interceptor for error handling
|
|
46
|
+
this.client.interceptors.response.use(response => response, error => this.handleError(error));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if NCEI service is available (token configured)
|
|
50
|
+
*/
|
|
51
|
+
isAvailable() {
|
|
52
|
+
return !!this.token && this.token.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get climate normals for a US location
|
|
56
|
+
*
|
|
57
|
+
* NOTE: This is a placeholder implementation. The NCEI API requires:
|
|
58
|
+
* 1. Station lookup from coordinates
|
|
59
|
+
* 2. Querying climate normals datasets by station
|
|
60
|
+
* 3. Data transformation to our format
|
|
61
|
+
*
|
|
62
|
+
* For v1.2.0, this throws DataNotFoundError to trigger fallback to Open-Meteo.
|
|
63
|
+
* Future enhancement: Implement full NCEI normals retrieval.
|
|
64
|
+
*
|
|
65
|
+
* @param latitude - Latitude (US only)
|
|
66
|
+
* @param longitude - Longitude (US only)
|
|
67
|
+
* @param month - Month (1-12)
|
|
68
|
+
* @param day - Day of month (1-31)
|
|
69
|
+
* @returns Climate normals
|
|
70
|
+
* @throws {DataNotFoundError} - NCEI integration not yet complete
|
|
71
|
+
*/
|
|
72
|
+
async getClimateNormals(latitude, longitude, month, day) {
|
|
73
|
+
if (!this.isAvailable()) {
|
|
74
|
+
throw new DataNotFoundError('NCEI', 'NCEI API token not configured. Set NCEI_API_TOKEN environment variable.');
|
|
75
|
+
}
|
|
76
|
+
logger.info('NCEI climate normals requested (not yet implemented)', {
|
|
77
|
+
latitude,
|
|
78
|
+
longitude,
|
|
79
|
+
month,
|
|
80
|
+
day
|
|
81
|
+
});
|
|
82
|
+
// TODO: Implement NCEI climate normals retrieval
|
|
83
|
+
// Steps needed:
|
|
84
|
+
// 1. Find nearby weather stations using /stations endpoint
|
|
85
|
+
// 2. Query climate normals datasets (NORMAL_DLY, NORMAL_MLY) for station
|
|
86
|
+
// 3. Extract and transform data to ClimateNormals format
|
|
87
|
+
// 4. Handle missing data and station selection logic
|
|
88
|
+
//
|
|
89
|
+
// Complexity: High - requires multiple API calls and data processing
|
|
90
|
+
// Estimated implementation: 1-2 days
|
|
91
|
+
//
|
|
92
|
+
// For now, throw DataNotFoundError to trigger fallback to Open-Meteo
|
|
93
|
+
throw new DataNotFoundError('NCEI', 'NCEI climate normals integration is planned for a future release. Using Open-Meteo fallback.');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Handle API errors
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
async handleError(error) {
|
|
100
|
+
if (error.response) {
|
|
101
|
+
const status = error.response.status;
|
|
102
|
+
const message = error.response.data?.message || error.message;
|
|
103
|
+
if (status === 429) {
|
|
104
|
+
throw new RateLimitError('NCEI', 'Rate limit exceeded (5 requests/second or 10,000/day)', 60);
|
|
105
|
+
}
|
|
106
|
+
if (status === 401 || status === 403) {
|
|
107
|
+
throw new ServiceUnavailableError('NCEI', 'Invalid or missing API token. Get a free token at https://www.ncdc.noaa.gov/cdo-web/token');
|
|
108
|
+
}
|
|
109
|
+
if (status === 404) {
|
|
110
|
+
throw new DataNotFoundError('NCEI', message || 'Data not found');
|
|
111
|
+
}
|
|
112
|
+
if (status >= 500) {
|
|
113
|
+
throw new ServiceUnavailableError('NCEI', message || 'Service temporarily unavailable');
|
|
114
|
+
}
|
|
115
|
+
throw new ServiceUnavailableError('NCEI', message);
|
|
116
|
+
}
|
|
117
|
+
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
|
|
118
|
+
throw new ServiceUnavailableError('NCEI', 'Request timed out');
|
|
119
|
+
}
|
|
120
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
121
|
+
throw new ServiceUnavailableError('NCEI', 'Unable to connect to NCEI API');
|
|
122
|
+
}
|
|
123
|
+
throw new ServiceUnavailableError('NCEI', error.message || 'Unknown error occurred');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=ncei.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ncei.js","sourceRoot":"","sources":["../../src/services/ncei.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAwB,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQnD;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IACd,MAAM,CAAgB;IACtB,KAAK,CAAqB;IAElC,YAAY,SAA4B,EAAE;QACxC,MAAM,EACJ,OAAO,GAAG,0CAA0C,EACpD,OAAO,GAAG,KAAK,EACf,KAAK,GAAG,cAAc,EACvB,GAAG,MAAM,CAAC;QAEX,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YACzB,OAAO;YACP,OAAO;YACP,OAAO,EAAE;gBACP,QAAQ,EAAE,kBAAkB;gBAC5B,YAAY,EAAE,YAAY,EAAE;aAC7B;SACF,CAAC,CAAC;QAEH,qCAAqC;QACrC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5D,CAAC;QAED,8CAA8C;QAC9C,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CACnC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EACpB,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CACjC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,iBAAiB,CACrB,QAAgB,EAChB,SAAiB,EACjB,KAAa,EACb,GAAW;QAEX,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,iBAAiB,CACzB,MAAM,EACN,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,sDAAsD,EAAE;YAClE,QAAQ;YACR,SAAS;YACT,KAAK;YACL,GAAG;SACJ,CAAC,CAAC;QAEH,iDAAiD;QACjD,gBAAgB;QAChB,2DAA2D;QAC3D,yEAAyE;QACzE,yDAAyD;QACzD,qDAAqD;QACrD,EAAE;QACF,qEAAqE;QACrE,qCAAqC;QACrC,EAAE;QACF,qEAAqE;QAErE,MAAM,IAAI,iBAAiB,CACzB,MAAM,EACN,8FAA8F,CAC/F,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,WAAW,CAAC,KAAU;QAClC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YACrC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC;YAE9D,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnB,MAAM,IAAI,cAAc,CACtB,MAAM,EACN,uDAAuD,EACvD,EAAE,CACH,CAAC;YACJ,CAAC;YAED,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBACrC,MAAM,IAAI,uBAAuB,CAC/B,MAAM,EACN,2FAA2F,CAC5F,CAAC;YACJ,CAAC;YAED,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnB,MAAM,IAAI,iBAAiB,CAAC,MAAM,EAAE,OAAO,IAAI,gBAAgB,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;gBAClB,MAAM,IAAI,uBAAuB,CAAC,MAAM,EAAE,OAAO,IAAI,iCAAiC,CAAC,CAAC;YAC1F,CAAC;YAED,MAAM,IAAI,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChE,MAAM,IAAI,uBAAuB,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAChE,MAAM,IAAI,uBAAuB,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,IAAI,uBAAuB,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,wBAAwB,CAAC,CAAC;IACvF,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for interacting with NIFC (National Interagency Fire Center) ArcGIS REST API
|
|
3
|
+
*/
|
|
4
|
+
import type { NIFCQueryResponse } from '../types/wildfire.js';
|
|
5
|
+
export interface NIFCServiceConfig {
|
|
6
|
+
baseURL?: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class NIFCService {
|
|
10
|
+
private client;
|
|
11
|
+
private cache;
|
|
12
|
+
private readonly featureServerUrl;
|
|
13
|
+
constructor(config?: NIFCServiceConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Get cache statistics
|
|
16
|
+
*/
|
|
17
|
+
getCacheStats(): import("../utils/cache.js").CacheStats;
|
|
18
|
+
/**
|
|
19
|
+
* Clear the cache
|
|
20
|
+
*/
|
|
21
|
+
clearCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Query fire perimeters within a bounding box
|
|
24
|
+
* @param west Western longitude boundary
|
|
25
|
+
* @param south Southern latitude boundary
|
|
26
|
+
* @param east Eastern longitude boundary
|
|
27
|
+
* @param north Northern latitude boundary
|
|
28
|
+
* @returns Fire perimeter features within the bounding box
|
|
29
|
+
*/
|
|
30
|
+
queryFirePerimeters(west: number, south: number, east: number, north: number): Promise<NIFCQueryResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Query the NIFC ArcGIS Feature Server
|
|
33
|
+
*/
|
|
34
|
+
private queryFeatureServer;
|
|
35
|
+
/**
|
|
36
|
+
* Check if the NIFC service is operational
|
|
37
|
+
*/
|
|
38
|
+
checkServiceStatus(): Promise<{
|
|
39
|
+
operational: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=nifc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nifc.d.ts","sourceRoot":"","sources":["../../src/services/nifc.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAM9D,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAQ;IAGrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6H;gBAElJ,MAAM,GAAE,iBAAsB;IAe1C;;OAEG;IACH,aAAa;IAIb;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;;;;;;OAOG;IACG,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,iBAAiB,CAAC;IAuC7B;;OAEG;YACW,kBAAkB;IA2DhC;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,WAAW,EAAE,OAAO,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CA8BH"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for interacting with NIFC (National Interagency Fire Center) ArcGIS REST API
|
|
3
|
+
*/
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { Cache } from '../utils/cache.js';
|
|
6
|
+
import { CacheConfig } from '../config/cache.js';
|
|
7
|
+
import { validateLatitude, validateLongitude } from '../utils/validation.js';
|
|
8
|
+
import { logger, redactCoordinatesForLogging } from '../utils/logger.js';
|
|
9
|
+
export class NIFCService {
|
|
10
|
+
client;
|
|
11
|
+
cache;
|
|
12
|
+
// NIFC WFIGS Feature Server URL for current fire perimeters
|
|
13
|
+
featureServerUrl = 'https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/WFIGS_Interagency_Perimeters_Current/FeatureServer/0';
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
const { timeout = CacheConfig.apiTimeoutMs } = config;
|
|
16
|
+
this.cache = new Cache(CacheConfig.maxSize);
|
|
17
|
+
this.client = axios.create({
|
|
18
|
+
timeout,
|
|
19
|
+
headers: {
|
|
20
|
+
'Accept': 'application/json'
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get cache statistics
|
|
26
|
+
*/
|
|
27
|
+
getCacheStats() {
|
|
28
|
+
return this.cache.getStats();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Clear the cache
|
|
32
|
+
*/
|
|
33
|
+
clearCache() {
|
|
34
|
+
this.cache.clear();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Query fire perimeters within a bounding box
|
|
38
|
+
* @param west Western longitude boundary
|
|
39
|
+
* @param south Southern latitude boundary
|
|
40
|
+
* @param east Eastern longitude boundary
|
|
41
|
+
* @param north Northern latitude boundary
|
|
42
|
+
* @returns Fire perimeter features within the bounding box
|
|
43
|
+
*/
|
|
44
|
+
async queryFirePerimeters(west, south, east, north) {
|
|
45
|
+
// Validate bounding box
|
|
46
|
+
validateLongitude(west);
|
|
47
|
+
validateLongitude(east);
|
|
48
|
+
validateLatitude(south);
|
|
49
|
+
validateLatitude(north);
|
|
50
|
+
if (west >= east) {
|
|
51
|
+
throw new Error('Invalid bounding box: west longitude must be less than east longitude');
|
|
52
|
+
}
|
|
53
|
+
if (south >= north) {
|
|
54
|
+
throw new Error('Invalid bounding box: south latitude must be less than north latitude');
|
|
55
|
+
}
|
|
56
|
+
// Check cache first (if enabled)
|
|
57
|
+
const bboxKey = `${west.toFixed(4)},${south.toFixed(4)},${east.toFixed(4)},${north.toFixed(4)}`;
|
|
58
|
+
if (CacheConfig.enabled) {
|
|
59
|
+
const cacheKey = Cache.generateKey('nifc-fire-perimeters', bboxKey);
|
|
60
|
+
const cached = this.cache.get(cacheKey);
|
|
61
|
+
if (cached) {
|
|
62
|
+
// Redact bounding box coordinates for privacy
|
|
63
|
+
const sw = redactCoordinatesForLogging(south, west);
|
|
64
|
+
const ne = redactCoordinatesForLogging(north, east);
|
|
65
|
+
logger.info('NIFC cache hit', {
|
|
66
|
+
bbox: `${sw.lon},${sw.lat},${ne.lon},${ne.lat}`
|
|
67
|
+
});
|
|
68
|
+
return cached;
|
|
69
|
+
}
|
|
70
|
+
const result = await this.queryFeatureServer(west, south, east, north);
|
|
71
|
+
// Cache for 30 minutes (fire data updates frequently)
|
|
72
|
+
this.cache.set(cacheKey, result, 1800000);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
return this.queryFeatureServer(west, south, east, north);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Query the NIFC ArcGIS Feature Server
|
|
79
|
+
*/
|
|
80
|
+
async queryFeatureServer(west, south, east, north) {
|
|
81
|
+
try {
|
|
82
|
+
// Build ArcGIS REST query
|
|
83
|
+
const params = new URLSearchParams({
|
|
84
|
+
f: 'json', // Response format
|
|
85
|
+
geometry: `${west},${south},${east},${north}`, // Bounding box
|
|
86
|
+
geometryType: 'esriGeometryEnvelope',
|
|
87
|
+
spatialRel: 'esriSpatialRelIntersects',
|
|
88
|
+
outFields: '*', // Return all fields
|
|
89
|
+
returnGeometry: 'true',
|
|
90
|
+
returnCentroid: 'false',
|
|
91
|
+
returnExceededLimitFeatures: 'false',
|
|
92
|
+
maxAllowableOffset: '0.0001', // Simplify geometry slightly for performance
|
|
93
|
+
where: '1=1' // No additional filters (query all active fires)
|
|
94
|
+
});
|
|
95
|
+
const url = `${this.featureServerUrl}/query?${params.toString()}`;
|
|
96
|
+
// Redact bounding box coordinates for privacy
|
|
97
|
+
const sw = redactCoordinatesForLogging(south, west);
|
|
98
|
+
const ne = redactCoordinatesForLogging(north, east);
|
|
99
|
+
logger.info('Querying NIFC fire perimeters', {
|
|
100
|
+
bbox: `${sw.lon},${sw.lat},${ne.lon},${ne.lat}`,
|
|
101
|
+
url: this.featureServerUrl
|
|
102
|
+
});
|
|
103
|
+
const response = await this.client.get(url);
|
|
104
|
+
logger.info('NIFC query complete', {
|
|
105
|
+
featureCount: response.data.features?.length || 0,
|
|
106
|
+
exceeded: response.data.exceededTransferLimit || false
|
|
107
|
+
});
|
|
108
|
+
return response.data;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger.error('NIFC query failed', error);
|
|
112
|
+
// Check for specific error types
|
|
113
|
+
if (axios.isAxiosError(error)) {
|
|
114
|
+
if (error.code === 'ECONNABORTED') {
|
|
115
|
+
throw new Error('NIFC service request timed out. The service may be temporarily unavailable.');
|
|
116
|
+
}
|
|
117
|
+
if (error.response?.status === 400) {
|
|
118
|
+
throw new Error('Invalid query parameters for NIFC service.');
|
|
119
|
+
}
|
|
120
|
+
if (error.response?.status === 503) {
|
|
121
|
+
throw new Error('NIFC service is temporarily unavailable. Please try again later.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`Failed to query NIFC fire perimeters: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if the NIFC service is operational
|
|
129
|
+
*/
|
|
130
|
+
async checkServiceStatus() {
|
|
131
|
+
try {
|
|
132
|
+
// Query a small bounding box to test service availability
|
|
133
|
+
const response = await this.client.get(`${this.featureServerUrl}?f=json`, {
|
|
134
|
+
timeout: 10000
|
|
135
|
+
});
|
|
136
|
+
if (response.status === 200 && response.data) {
|
|
137
|
+
return {
|
|
138
|
+
operational: true,
|
|
139
|
+
message: 'NIFC ArcGIS service is operational',
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
operational: false,
|
|
145
|
+
message: 'NIFC service returned unexpected response',
|
|
146
|
+
timestamp: new Date().toISOString()
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
logger.warn('NIFC service check failed', { error: error instanceof Error ? error.message : String(error) });
|
|
151
|
+
return {
|
|
152
|
+
operational: false,
|
|
153
|
+
message: `NIFC service is unavailable: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
154
|
+
timestamp: new Date().toISOString()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=nifc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nifc.js","sourceRoot":"","sources":["../../src/services/nifc.ts"],"names":[],"mappings":"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"}
|