@@ -5,7 +5,7 @@ import { addResource } from '../../index';
55// Schema for validating POWER request parameters
66export const powerParamsSchema = z . object ( {
77 parameters : z . string ( ) ,
8- community : z . enum ( [ 're' , 'sb' , 'ag' ] ) ,
8+ community : z . enum ( [ 're' , 'sb' , 'ag' , 'RE' , 'SB' , 'AG' ] ) . transform ( val => val . toLowerCase ( ) ) ,
99 format : z . enum ( [ 'json' , 'csv' , 'ascii' , 'netcdf' ] ) . optional ( ) . default ( 'json' ) ,
1010 // Location parameters
1111 latitude : z . number ( ) . min ( - 90 ) . max ( 90 ) . optional ( ) ,
@@ -23,6 +23,96 @@ export const powerParamsSchema = z.object({
2323
2424export type PowerParams = z . infer < typeof powerParamsSchema > ;
2525
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+
26116/**
27117 * Handle requests for NASA's POWER (Prediction Of Worldwide Energy Resources) API
28118 * Provides solar and meteorological data sets
@@ -32,62 +122,100 @@ export async function nasaPowerHandler(params: PowerParams) {
32122 // POWER API base URL
33123 const POWER_API_URL = 'https://power.larc.nasa.gov/api/temporal/daily/point' ;
34124
125+ // Validate and normalize parameters using the schema
126+ const validatedParams = powerParamsSchema . parse ( params ) ;
127+
35128 // Call the NASA POWER API
36129 const response = await axios ( {
37130 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
40134 } ) ;
41135
42136 // Create a resource ID based on key parameters
43137 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 } ` ) ;
49143
50- const resourceId = `nasa://power/${ params . community } ?${ resourceParams . join ( '&' ) } ` ;
144+ const resourceId = `nasa://power/${ validatedParams . community } ?${ resourceParams . join ( '&' ) } ` ;
51145
52146 // Register the response as a resource
53147 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
57151 } ) ;
58152
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 ) ;
74155
75- // Return the result
156+ // Return the formatted result
76157 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+ ] ,
81164 isError : false
82165 } ;
83166 } 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+
84179 console . error ( 'Error in POWER handler:' , error ) ;
85180
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+
86214 return {
87215 isError : true ,
88216 content : [ {
89217 type : "text" ,
90- text : `Error: ${ error . message || 'An unexpected error occurred' } `
218+ text : `Error: ${ errorMessage } `
91219 } ]
92220 } ;
93221 }
0 commit comments