/**
* PRISM (Parameter-elevation Relationships on Independent Slopes Model) datasource
* Provides access to high-resolution climate and weather data for the United States.
*
* **Data Information:**
* - **Source:** Oregon State University / PRISM Climate Group
* - **Format:** GeoTIFF (New) / BIL (Legacy)
* - **Resolution:** 400m (15s), 800m (30s), 4km (2.5m)
* - **Coverage:** CONUS, AK, HI, PR
* - **Time Range:** 1895 - Present
*
* **Available Data Types:**
* - `prism-current`: New format (GeoTIFF), covers US territories.
* - `prism-legacy`: Legacy format (BIL), CONUS only (Deprecated after Sept 2025).
*
* **Key Variables:**
* - `ppt`: Total Precipitation (mm)
* - `tmean` / `tmin` / `tmax`: Temperature (°C)
* - `tdmean`: Mean Dewpoint
* - `vpdmin` / `vpdmax`: Vapor Pressure Deficit
*
* @example
* // 1. Retrieve Recent Daily Max Temperature (New Format)
* const tempData = await hydro.data.retrieve({
* params: {
* source: 'prism',
* datatype: 'grid-data'
* },
* args: {
* dataset: 'prism-current',
* variable: 'tmax',
* bbox: [-123.0, 44.0, -121.0, 46.0], // Oregon
* startDate: '2023-07-15T00:00:00Z',
* resolution: '30s' // 800m
* }
* });
*
* @example
* // 2. Retrieve Monthly Precipitation Normals (30-year)
* const normals = await hydro.data.retrieve({
* params: {
* source: 'prism',
* datatype: 'grid-data'
* },
* args: {
* dataset: 'prism-current',
* variable: 'ppt',
* date: 'normals', // Special keyword for normals
* resolution: '25m', // 4km
* format: 'geotiff'
* }
* });
*
* @see https://prism.oregonstate.edu/
* @type {Object}
* @name PRISM
* @memberof datasources
*/
// Base URLs for PRISM data access
const PRISM_BASE_URLS = {
// New format (March 2025+)
new: "https://prism.oregonstate.edu/fetchData.php",
// Legacy format (deprecated Sept 30, 2025)
legacy: "https://prism.oregonstate.edu/fetchData.php"
};
// PRISM Dataset configurations
const PRISM_DATASETS = {
"prism-current": {
baseUrl: PRISM_BASE_URLS.new,
description: "PRISM Current Data - CONUS, Alaska, Hawaii, Puerto Rico (New Format)",
format: "new", // Use new naming scheme
spatial: {
us: {
latitude: { min: 24.0, max: 50.0 },
longitude: { min: -125.0, max: -66.0 }
},
ak: {
latitude: { min: 51.0, max: 72.0 },
longitude: { min: -180.0, max: -129.0 }
},
hi: {
latitude: { min: 18.0, max: 23.0 },
longitude: { min: -161.0, max: -154.0 }
},
pr: {
latitude: { min: 17.0, max: 19.0 },
longitude: { min: -68.0, max: -65.0 }
}
},
temporal: {
start: "1895-01-01T00:00:00Z",
end: new Date().toISOString(), // Current date
resolution: "1D", // Daily data
normals: {
period: "1991-2020", // 30-year normals
available: true
}
},
resolutions: {
"15s": { description: "400m resolution", meters: 400 },
"30s": { description: "800m resolution", meters: 800 },
"25m": { description: "4km resolution", meters: 4000 }
},
regions: ["us", "ak", "hi", "pr"],
fileFormat: "tif" // Cloud Optimized GeoTIFF
},
"prism-legacy": {
baseUrl: PRISM_BASE_URLS.legacy,
description: "PRISM Legacy Data - CONUS (Legacy Format, deprecated Sept 2025)",
format: "legacy", // Use legacy naming scheme
spatial: {
us: {
latitude: { min: 24.0, max: 50.0 },
longitude: { min: -125.0, max: -66.0 }
}
},
temporal: {
start: "1895-01-01T00:00:00Z",
end: "2025-09-30T00:00:00Z", // Deprecated after this date
resolution: "1D",
normals: {
period: "1981-2010", // 30-year normals
available: true
}
},
resolutions: {
"800mM2": { description: "800m resolution monthly", meters: 800 },
"4kmM2": { description: "4km resolution monthly", meters: 4000 },
"4kmD1": { description: "4km resolution daily", meters: 4000 }
},
regions: ["us"],
fileFormat: "bil" // Binary Interleaved by Line
}
};
// Available variables and their properties
const PRISM_VARIABLES = {
// Precipitation, temperature, and humidity variables
"ppt": {
longName: "Total Precipitation",
units: "mm",
description: "Daily/monthly total precipitation",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"tmin": {
longName: "Minimum Temperature",
units: "°C",
description: "Daily/monthly minimum temperature",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"tmean": {
longName: "Mean Temperature",
units: "°C",
description: "Daily/monthly mean temperature",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"tmax": {
longName: "Maximum Temperature",
units: "°C",
description: "Daily/monthly maximum temperature",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"tdmean": {
longName: "Mean Dewpoint Temperature",
units: "°C",
description: "Daily/monthly mean dewpoint temperature",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"vpdmin": {
longName: "Minimum Vapor Pressure Deficit",
units: "hPa",
description: "Daily/monthly minimum vapor pressure deficit",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
"vpdmax": {
longName: "Maximum Vapor Pressure Deficit",
units: "hPa",
description: "Daily/monthly maximum vapor pressure deficit",
availability: ["normals", "monthly", "daily"],
scaleFactor: 1.0,
fillValue: -9999
},
// Solar radiation variables (normals only)
"solclear": {
longName: "Clear Sky Solar Radiation",
units: "MJ/m²/day",
description: "Clear sky solar radiation",
availability: ["normals"],
scaleFactor: 1.0,
fillValue: -9999
},
"soltotal": {
longName: "Total Solar Radiation",
units: "MJ/m²/day",
description: "Total solar radiation",
availability: ["normals"],
scaleFactor: 1.0,
fillValue: -9999
},
"solslope": {
longName: "Sloped Surface Solar Radiation",
units: "MJ/m²/day",
description: "Sloped surface solar radiation",
availability: ["normals"],
scaleFactor: 1.0,
fillValue: -9999
},
"soltrans": {
longName: "Cloud Transmittance",
units: "dimensionless",
description: "Cloud transmittance for solar radiation",
availability: ["normals"],
scaleFactor: 1.0,
fillValue: -9999
}
};
// Data stability levels (legacy format only)
const PRISM_STABILITY_LEVELS = {
"early": "Preliminary data, available within 1-2 days",
"provisional": "Quality-controlled data, available within 1-2 weeks",
"stable": "Final quality-controlled data, available within 1-2 months",
"30yr_normal": "30-year climatological normals"
};
/**
* Generate PRISM file URL based on parameters
* @param {string} variable - Variable name (ppt, tmin, etc.)
* @param {string} region - Region code (us, ak, hi, pr)
* @param {string} resolution - Resolution code (15s, 30s, 25m)
* @param {string} timePeriod - Time period string
* @param {string} format - Format type ('new' or 'legacy')
* @param {string} stability - Stability level (legacy only)
* @returns {string} Complete download URL
*/
export function generatePRISMFileURL(variable, region, resolution, timePeriod, format = 'new', stability = 'stable') {
let filename;
if (format === 'new') {
// New format: prism_<var>_<region>_<resolution>_<time period>.zip
filename = `prism_${variable}_${region}_${resolution}_${timePeriod}.zip`;
} else {
// Legacy format: PRISM_<var>_<stability>_<scale&version>_<time period>[_all][_<format>].zip
const scaleVersion = resolution; // e.g., "4kmD1", "800mM2"
filename = `PRISM_${variable}_${stability}_${scaleVersion}_${timePeriod}_bil.zip`;
}
const baseUrl = format === 'new' ? PRISM_BASE_URLS.new : PRISM_BASE_URLS.legacy;
return `${baseUrl}?file=${filename}`;
}
/**
* Parse date to PRISM time period format
* @param {Date} date - Input date
* @param {string} dataType - Type of data ('daily', 'monthly', 'annual', 'normals')
* @returns {string} Formatted time period string
*/
export function formatPRISMTimePeriod(date, dataType) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
switch (dataType) {
case 'daily':
return `${year}${month}${day}`;
case 'monthly':
return `${year}${month}`;
case 'annual':
return `${year}`;
case 'normals':
return month; // For monthly normals (01-12) or 'annual'
default:
throw new Error(`Unknown PRISM data type: ${dataType}`);
}
}
/**
* Determine appropriate region based on bounding box
* @param {Array} bbox - Bounding box [west, south, east, north]
* @returns {string} Region code (us, ak, hi, pr)
*/
export function determinePRISMRegion(bbox) {
const [west, south, east, north] = bbox;
// Alaska
if (west >= -180 && east <= -129 && south >= 51 && north <= 72) {
return 'ak';
}
// Hawaii
if (west >= -161 && east <= -154 && south >= 18 && north <= 23) {
return 'hi';
}
// Puerto Rico
if (west >= -68 && east <= -65 && south >= 17 && north <= 19) {
return 'pr';
}
// Default to CONUS
return 'us';
}
/**
* Validate PRISM variable availability for data type
* @param {string} variable - Variable name
* @param {string} dataType - Data type ('normals', 'monthly', 'daily')
* @returns {boolean} True if variable is available for data type
*/
export function validatePRISMVariable(variable, dataType) {
const variableConfig = PRISM_VARIABLES[variable];
if (!variableConfig) {
return false;
}
return variableConfig.availability.includes(dataType);
}
/**
* Get recommended resolution for region and data type
* @param {string} region - Region code
* @param {string} dataType - Data type
* @param {string} format - Format type ('new' or 'legacy')
* @returns {string} Recommended resolution code
*/
export function getRecommendedPRISMResolution(region, dataType, format = 'new') {
if (format === 'new') {
// Higher resolution for smaller regions
if (region === 'hi' || region === 'pr') {
return '15s'; // 400m
}
return '25m'; // 4km for larger regions
} else {
// Legacy format
if (dataType === 'daily') {
return '4kmD1';
}
return '4kmM2';
}
}
// Export the PRISM datasource configuration
export default {
datasets: PRISM_DATASETS,
variables: PRISM_VARIABLES,
stability: PRISM_STABILITY_LEVELS,
baseUrls: PRISM_BASE_URLS,
// Utility functions
generateURL: generatePRISMFileURL,
formatTimePeriod: formatPRISMTimePeriod,
determineRegion: determinePRISMRegion,
validateVariable: validatePRISMVariable,
getRecommendedResolution: getRecommendedPRISMResolution
};