@@ -5,7 +5,7 @@ import { addResource } from '../../index';
5
5
// Schema for validating POWER request parameters
6
6
export const powerParamsSchema = z . object ( {
7
7
parameters : z . string ( ) ,
8
- community : z . enum ( [ 're' , 'sb' , 'ag' ] ) ,
8
+ community : z . enum ( [ 're' , 'sb' , 'ag' , 'RE' , 'SB' , 'AG' ] ) . transform ( val => val . toLowerCase ( ) ) ,
9
9
format : z . enum ( [ 'json' , 'csv' , 'ascii' , 'netcdf' ] ) . optional ( ) . default ( 'json' ) ,
10
10
// Location parameters
11
11
latitude : z . number ( ) . min ( - 90 ) . max ( 90 ) . optional ( ) ,
@@ -23,6 +23,96 @@ export const powerParamsSchema = z.object({
23
23
24
24
export type PowerParams = z . infer < typeof powerParamsSchema > ;
25
25
26
+ /**
27
+ * Format POWER API data into a human-readable text
28
+ */
29
+ function formatPowerDataText ( responseData : any , params : PowerParams ) : string {
30
+ try {
31
+ const properties = responseData . properties || { } ;
32
+ const parameterData = properties . parameter || { } ;
33
+ const geometry = responseData . geometry || { } ;
34
+ const header = responseData . header || { } ;
35
+
36
+ const requestedParams = params . parameters . split ( ',' ) ;
37
+
38
+ let locationStr = 'Global' ;
39
+ if ( geometry . type === 'Point' && geometry . coordinates ) {
40
+ locationStr = `Point (${ geometry . coordinates [ 1 ] } , ${ geometry . coordinates [ 0 ] } )` ;
41
+ } else if ( params . bbox ) {
42
+ locationStr = `Region (${ params . bbox } )` ;
43
+ }
44
+
45
+ let dateRangeStr = '' ;
46
+ if ( header . start && header . end ) {
47
+ dateRangeStr = `${ header . start } to ${ header . end } ` ;
48
+ } else if ( params . start && params . end ) {
49
+ dateRangeStr = `${ params . start } to ${ params . end } ` ;
50
+ }
51
+
52
+ let text = `# NASA POWER Data\n\n` ;
53
+ text += `**Community:** ${ params . community . toUpperCase ( ) } \n` ;
54
+ text += `**Location:** ${ locationStr } \n` ;
55
+ text += `**Date Range:** ${ dateRangeStr || 'N/A' } \n\n` ;
56
+
57
+ text += `## Parameters\n\n` ;
58
+
59
+ requestedParams . forEach ( paramKey => {
60
+ const data = parameterData [ paramKey ] ;
61
+ const unit = header . parameter_information ?. [ paramKey ] ?. units || 'N/A' ;
62
+ const longName = header . parameter_information ?. [ paramKey ] ?. long_name || paramKey ;
63
+
64
+ text += `### ${ longName } (${ paramKey } )\n` ;
65
+ text += `- **Units:** ${ unit } \n` ;
66
+
67
+ if ( data && typeof data === 'object' ) {
68
+ const dates = Object . keys ( data ) . sort ( ) ;
69
+ if ( dates . length > 0 ) {
70
+ text += `- **Data:**\n` ;
71
+ text += '| Date | Value |\n' ;
72
+ text += '|------------|-------|\n' ;
73
+ // Show first 10 and last 10 dates to avoid excessive length
74
+ const maxEntries = 10 ;
75
+ const totalEntries = dates . length ;
76
+ let entriesShown = 0 ;
77
+
78
+ for ( let i = 0 ; i < Math . min ( maxEntries , totalEntries ) ; i ++ ) {
79
+ const date = dates [ i ] ;
80
+ const value = data [ date ] !== undefined ? data [ date ] : 'N/A' ;
81
+ text += `| ${ date } | ${ value } |
82
+ ` ;
83
+ entriesShown ++ ;
84
+ }
85
+
86
+ if ( totalEntries > maxEntries * 2 ) {
87
+ text += `| ... | ... |\n` ; // Indicate truncation
88
+ }
89
+
90
+ if ( totalEntries > maxEntries ) {
91
+ const startIndex = Math . max ( maxEntries , totalEntries - maxEntries ) ;
92
+ for ( let i = startIndex ; i < totalEntries ; i ++ ) {
93
+ const date = dates [ i ] ;
94
+ const value = data [ date ] !== undefined ? data [ date ] : 'N/A' ;
95
+ text += `| ${ date } | ${ value } |
96
+ ` ;
97
+ entriesShown ++ ;
98
+ }
99
+ }
100
+ text += `\n*(Showing ${ entriesShown } of ${ totalEntries } daily values)*\n\n` ;
101
+ } else {
102
+ text += `- Data: No data available for this period.\n\n` ;
103
+ }
104
+ } else {
105
+ text += `- Data: Not available or invalid format.\n\n` ;
106
+ }
107
+ } ) ;
108
+
109
+ return text ;
110
+ } catch ( formatError : any ) {
111
+ console . error ( 'Error formatting POWER data:' , formatError ) ;
112
+ return `Error: Failed to format POWER data. Raw data might be available in resources. Error: ${ formatError . message } ` ;
113
+ }
114
+ }
115
+
26
116
/**
27
117
* Handle requests for NASA's POWER (Prediction Of Worldwide Energy Resources) API
28
118
* Provides solar and meteorological data sets
@@ -32,62 +122,100 @@ export async function nasaPowerHandler(params: PowerParams) {
32
122
// POWER API base URL
33
123
const POWER_API_URL = 'https://power.larc.nasa.gov/api/temporal/daily/point' ;
34
124
125
+ // Validate and normalize parameters using the schema
126
+ const validatedParams = powerParamsSchema . parse ( params ) ;
127
+
35
128
// Call the NASA POWER API
36
129
const response = await axios ( {
37
130
url : POWER_API_URL ,
38
- params : params ,
39
- method : 'GET'
131
+ params : validatedParams , // Use validated & normalized params
132
+ method : 'GET' ,
133
+ timeout : 30000 // Increased timeout to 30 seconds
40
134
} ) ;
41
135
42
136
// Create a resource ID based on key parameters
43
137
const resourceParams = [ ] ;
44
- if ( params . parameters ) resourceParams . push ( `parameters=${ params . parameters } ` ) ;
45
- if ( params . latitude !== undefined ) resourceParams . push ( `lat=${ params . latitude } ` ) ;
46
- if ( params . longitude !== undefined ) resourceParams . push ( `lon=${ params . longitude } ` ) ;
47
- if ( params . start ) resourceParams . push ( `start=${ params . start } ` ) ;
48
- if ( params . end ) resourceParams . push ( `end=${ params . end } ` ) ;
138
+ if ( validatedParams . parameters ) resourceParams . push ( `parameters=${ validatedParams . parameters } ` ) ;
139
+ if ( validatedParams . latitude !== undefined ) resourceParams . push ( `lat=${ validatedParams . latitude } ` ) ;
140
+ if ( validatedParams . longitude !== undefined ) resourceParams . push ( `lon=${ validatedParams . longitude } ` ) ;
141
+ if ( validatedParams . start ) resourceParams . push ( `start=${ validatedParams . start } ` ) ;
142
+ if ( validatedParams . end ) resourceParams . push ( `end=${ validatedParams . end } ` ) ;
49
143
50
- const resourceId = `nasa://power/${ params . community } ?${ resourceParams . join ( '&' ) } ` ;
144
+ const resourceId = `nasa://power/${ validatedParams . community } ?${ resourceParams . join ( '&' ) } ` ;
51
145
52
146
// Register the response as a resource
53
147
addResource ( resourceId , {
54
- name : `NASA POWER ${ params . community . toUpperCase ( ) } Data${ params . latitude !== undefined ? ` at (${ params . latitude } , ${ params . longitude } )` : '' } ` ,
55
- mimeType : params . format === 'json' ? 'application/json' : 'text/plain' ,
56
- text : params . format === 'json' ? JSON . stringify ( response . data , null , 2 ) : response . data
148
+ name : `NASA POWER ${ validatedParams . community . toUpperCase ( ) } Data${ validatedParams . latitude !== undefined ? ` at (${ validatedParams . latitude } , ${ validatedParams . longitude } )` : '' } ` ,
149
+ mimeType : validatedParams . format === 'json' ? 'application/json' : 'text/plain' ,
150
+ text : validatedParams . format === 'json' ? JSON . stringify ( response . data , null , 2 ) : response . data
57
151
} ) ;
58
152
59
- // Extract metadata for more informative response
60
- let paramNames = '' ;
61
- if ( params . parameters ) {
62
- paramNames = params . parameters . split ( ',' ) . join ( ', ' ) ;
63
- }
64
-
65
- let locationStr = '' ;
66
- if ( params . latitude !== undefined && params . longitude !== undefined ) {
67
- locationStr = `(${ params . latitude } , ${ params . longitude } )` ;
68
- }
69
-
70
- let dateRangeStr = '' ;
71
- if ( params . start && params . end ) {
72
- dateRangeStr = `from ${ params . start } to ${ params . end } ` ;
73
- }
153
+ // Format the data for display
154
+ const formattedText = formatPowerDataText ( response . data , validatedParams ) ;
74
155
75
- // Return the result
156
+ // Return the formatted result
76
157
return {
77
- content : [ {
78
- type : "text" ,
79
- text : `Retrieved POWER ${ params . community . toUpperCase ( ) } data${ locationStr ? ` for location ${ locationStr } ` : '' } ${ dateRangeStr ? ` ${ dateRangeStr } ` : '' } ${ paramNames ? ` with parameters: ${ paramNames } ` : '' } .`
80
- } ] ,
158
+ content : [
159
+ {
160
+ type : "text" ,
161
+ text : formattedText
162
+ }
163
+ ] ,
81
164
isError : false
82
165
} ;
83
166
} catch ( error : any ) {
167
+ // Handle Zod validation errors separately
168
+ if ( error instanceof z . ZodError ) {
169
+ console . error ( 'Error validating POWER parameters:' , error . errors ) ;
170
+ return {
171
+ isError : true ,
172
+ content : [ {
173
+ type : "text" ,
174
+ text : `Error: Invalid parameters for NASA POWER API. Issues: ${ error . errors . map ( e => `${ e . path . join ( '.' ) } - ${ e . message } ` ) . join ( '; ' ) } `
175
+ } ]
176
+ } ;
177
+ }
178
+
84
179
console . error ( 'Error in POWER handler:' , error ) ;
85
180
181
+ let errorMessage = 'An unexpected error occurred' ;
182
+
183
+ if ( error . response ) {
184
+ // The request was made and the server responded with an error status
185
+ console . error ( 'Response status:' , error . response . status ) ;
186
+ console . error ( 'Response headers:' , JSON . stringify ( error . response . headers ) ) ;
187
+ console . error ( 'Response data:' , JSON . stringify ( error . response . data ) . substring ( 0 , 200 ) ) ;
188
+
189
+ // Check content type before assuming JSON for error response
190
+ const contentType = error . response . headers ?. [ 'content-type' ] || '' ;
191
+ let errorDetails = 'Unknown error' ;
192
+ if ( contentType . includes ( 'application/json' ) && error . response . data ) {
193
+ errorDetails = error . response . data ?. message || error . response . data ?. errors ?. join ( ', ' ) ||
194
+ JSON . stringify ( error . response . data ) . substring ( 0 , 100 ) ;
195
+ } else if ( typeof error . response . data === 'string' ) {
196
+ errorDetails = error . response . data . substring ( 0 , 150 ) ; // Show raw text if not JSON
197
+ }
198
+
199
+ errorMessage = `NASA POWER API error (${ error . response . status } ): ${ errorDetails } ` ;
200
+ } else if ( error . request ) {
201
+ // The request was made but no response was received
202
+ console . error ( 'Request details:' ) ;
203
+ console . error ( '- URL:' , error . config ?. url || 'Not available' ) ; // Use config for URL
204
+ console . error ( '- Params:' , error . config ?. params || 'Not available' ) ;
205
+ console . error ( '- Method:' , error . request . method || 'Not available' ) ;
206
+ console . error ( '- Headers:' , error . request . _header || 'Not available' ) ;
207
+
208
+ errorMessage = `NASA POWER API network error: No response received or request timed out. Check network connectivity and API status. URL: ${ error . config ?. url } ` ;
209
+ } else {
210
+ // Something happened in setting up the request
211
+ errorMessage = `NASA POWER API request setup error: ${ error . message } ` ;
212
+ }
213
+
86
214
return {
87
215
isError : true ,
88
216
content : [ {
89
217
type : "text" ,
90
- text : `Error: ${ error . message || 'An unexpected error occurred' } `
218
+ text : `Error: ${ errorMessage } `
91
219
} ]
92
220
} ;
93
221
}
0 commit comments