001 /** 002 * 003 */ 004 package org.wdssii.nse; 005 006 import org.apache.commons.logging.Log; 007 import org.apache.commons.logging.LogFactory; 008 import org.wdssii.core.DataType; 009 010 /** 011 * 012 * Creates and maintains a NSE profile 013 * 014 * @author lakshman 015 * 016 */ 017 public class NSEProfile { 018 private static Log log = LogFactory.getLog(NSEProfile.class); 019 float[] pressureLevels; 020 float[][] sfcElevation; 021 float[][] mslPressure; 022 float[][] sfcUWind; 023 float[][] sfcVWind; 024 float[][] sfcPressure; 025 float[][] sfcTemperature; 026 float[][] sfcDewPoint; 027 028 float[][][] temperatureC, dewpoint, height, u, v, w; 029 030 /** 031 * Invoke this constructor, set the fields sfcElevation, mslPressure, ... 032 * sfcDewPoint and then call finishInit() 033 * 034 * @param pressureLevels 035 */ 036 NSEProfile(float[] pressureLevels) { 037 this.pressureLevels = pressureLevels; 038 temperatureC = new float[pressureLevels.length][][]; 039 dewpoint = new float[pressureLevels.length][][]; 040 height = new float[pressureLevels.length][][]; 041 u = new float[pressureLevels.length][][]; 042 v = new float[pressureLevels.length][][]; 043 w = new float[pressureLevels.length][][]; 044 } 045 046 private int numLat, numLon; 047 private float[][][] satVaporPressure, vaporPressure, rh, lclTemp, lclPres, 048 theta, mixingRatio, virtualTempK, thetaE, wetBulbTempK, 049 moistParcelTemp; 050 private int[][] nearSurfaceGridPoint; // index of first pressureLevel above ground 051 052 /** Call after setting all fields. */ 053 void finishInit() { 054 long start = System.nanoTime(); 055 log.info("Starting to compute 3D NSE points ..."); 056 057 numLat = temperatureC[0].length; 058 numLon = temperatureC[0][0].length; 059 060 satVaporPressure = new float[pressureLevels.length][numLat][numLon]; 061 vaporPressure = new float[pressureLevels.length][numLat][numLon]; 062 rh = new float[pressureLevels.length][numLat][numLon]; 063 lclTemp = new float[pressureLevels.length][numLat][numLon]; 064 lclPres = new float[pressureLevels.length][numLat][numLon]; 065 theta = new float[pressureLevels.length][numLat][numLon]; 066 mixingRatio = new float[pressureLevels.length][numLat][numLon]; 067 virtualTempK = new float[pressureLevels.length][numLat][numLon]; 068 thetaE = new float[pressureLevels.length][numLat][numLon]; 069 wetBulbTempK = new float[pressureLevels.length][numLat][numLon]; 070 moistParcelTemp = new float[pressureLevels.length][numLat][numLon]; 071 nearSurfaceGridPoint = new int[numLat][numLon]; 072 073 for (int i = 0; i < numLat; ++i) { 074 for (int j = 0; j < numLon; ++j) { 075 float ground = sfcElevation[i][j]; 076 nearSurfaceGridPoint[i][j] = 0; 077 for (int k = 0; k < pressureLevels.length; ++k) { 078 if (height[k][i][j] < ground) { 079 temperatureC[k][i][j] = dewpoint[k][i][j] = u[k][i][j] = v[k][i][j] = DataType.MissingData; 080 satVaporPressure[k][i][j] = vaporPressure[k][i][j] = rh[k][i][j] = lclTemp[k][i][j] = theta[k][i][j] = mixingRatio[k][i][j] = virtualTempK[k][i][j] = thetaE[k][i][j] = wetBulbTempK[k][i][j] = moistParcelTemp[k][i][j] = DataType.MissingData; 081 } else { 082 if (nearSurfaceGridPoint[i][j] == 0 ){ 083 nearSurfaceGridPoint[i][j] = k; // index of first point above ground 084 } 085 initNsePoint(k, i, j); 086 } 087 } 088 } 089 } 090 long end = System.nanoTime(); 091 log.info("Took " + (end - start) * 0.001 * 0.001 092 + " milliseconds to compute 3D NSE points"); 093 initSfcNsePoints(); 094 } 095 096 private void initNsePoint(int k, int i, int j) { 097 // order is important as later variables depends on former ones 098 satVaporPressure[k][i][j] = calcSaturationVaporPressure(temperatureC[k][i][j]); 099 vaporPressure[k][i][j] = calcVaporPressure(dewpoint[k][i][j]); 100 rh[k][i][j] = calcRHFromVaporPressures(vaporPressure[k][i][j], satVaporPressure[k][i][j]); 101 lclTemp[k][i][j] = calcLclTemp(temperatureC[k][i][j], dewpoint[k][i][j]); 102 lclPres[k][i][j] = calcLclPres(lclTemp[k][i][j], pressureLevels[k], 103 temperatureC[k][i][j] + 273.15f); 104 theta[k][i][j] = calcPotentialTemperature(pressureLevels[k], 105 temperatureC[k][i][j] + 273.15f); 106 mixingRatio[k][i][j] = calcMixingRatio(vaporPressure[k][i][j], 107 pressureLevels[k]); 108 virtualTempK[k][i][j] = calcVirtualTemperature(temperatureC[k][i][j], 109 mixingRatio[k][i][j]); 110 thetaE[k][i][j] = calcEquivalentPotentialTemperature( 111 temperatureC[k][i][j], mixingRatio[k][i][j], lclTemp[k][i][j], 112 pressureLevels[k]); 113 wetBulbTempK[k][i][j] = calcWetBulb(vaporPressure[k][i][j], 114 satVaporPressure[k][i][j], dewpoint[k][i][j], 115 temperatureC[k][i][j], pressureLevels[k]); 116 moistParcelTemp[k][i][j] = calcMoistParcelTemp(thetaE[k][i][j], 117 pressureLevels[k]); 118 } 119 120 float[][] sfcRelativeHumidity, sfcVirtualTemperature, sfcTheta, sfcThetaE, sfcMixingRatio, sfcLclTemperature, sfcLclPressure, sfcWetBulbTemperatureK; 121 private void initSfcNsePoints() { 122 long start = System.nanoTime(); 123 log.info("Starting to compute Surface NSE points ..."); 124 sfcRelativeHumidity = new float[numLat][numLon]; 125 sfcMixingRatio = new float[numLat][numLon]; 126 sfcVirtualTemperature = new float[numLat][numLon]; 127 sfcTheta = new float[numLat][numLon]; 128 sfcThetaE = new float[numLat][numLon]; 129 sfcLclTemperature = new float[numLat][numLon]; 130 sfcLclPressure = new float[numLat][numLon]; 131 sfcWetBulbTemperatureK = new float[numLat][numLon]; 132 133 for (int i = 0; i < numLat; ++i) { 134 for (int j = 0; j < numLon; ++j) { 135 float vaporPressure = calcVaporPressure(sfcDewPoint[i][j]); 136 float saturationVaporPressure = calcSaturationVaporPressure(sfcTemperature[i][j]); 137 sfcRelativeHumidity[i][j] = calcRHFromVaporPressures(vaporPressure, saturationVaporPressure); 138 sfcMixingRatio[i][j] = calcMixingRatio(vaporPressure, sfcPressure[i][j]); 139 sfcVirtualTemperature[i][j] = calcVirtualTemperature(sfcTemperature[i][j], sfcMixingRatio[i][j]); 140 sfcTheta[i][j] = calcPotentialTemperature(sfcPressure[i][j], sfcTemperature[i][j] + 273.15f); 141 sfcThetaE[i][j] = calcEquivalentPotentialTemperature(sfcTemperature[i][j], sfcRelativeHumidity[i][j], sfcPressure[i][j]); 142 sfcLclTemperature[i][j] = calcLclTemp(sfcTemperature[i][j], sfcDewPoint[i][j]); 143 sfcLclPressure[i][j] = calcLclPres(sfcLclTemperature[i][j], sfcPressure[i][j], sfcTemperature[i][j] + 273.15f); 144 } 145 } 146 long end = System.nanoTime(); 147 log.info("Took " + (end - start) * 0.001 * 0.001 148 + " milliseconds to compute Surface NSE points"); 149 } 150 151 private static float calcDewPoint(float temp, float rh) { 152 if ((temp != DataType.MissingData) && (rh != DataType.MissingData)) { 153 double RH = rh * 0.01; 154 // from Bolton (MWR 1980): 155 double es = 6.112 * Math.exp((17.67 * temp) / (temp + 243.5)); 156 double e = es * RH; 157 double dewp = (243.5 * Math.log(e) - 440.8) / (19.48 - Math.log(e)); 158 return (float) dewp; 159 } 160 return DataType.MissingData; 161 } 162 163 private static float calcMoistParcelTemp(float thetae, float pres) { 164 165 // calculate the parcel temperature at the given pressure for a 166 // given parcel thetaE, using Newton-Rhapson interation. 167 // 168 if (pres == DataType.MissingData || thetae == DataType.MissingData) { 169 return DataType.MissingData; 170 } 171 double maxt = 0; 172 if ((thetae - 270) > 0) 173 maxt = thetae - 270; 174 175 // generate a first-guess the GEMPAK way (and in degreeC) 176 float tguess = (float) ((thetae - 0.5 * Math.pow(maxt, 1.05)) 177 * Math.pow(pres / 1000, 0.2) - 273.15); 178 // set convergence 179 double conv = 0.01; 180 // 297.621 / 900 181 // 182 double oldguess1 = -999999999999999.0; 183 double oldguess2 = -999999999999999.0; 184 // iterate by degrees 185 for (int i = 1; i <= 100; ++i) { 186 float rh = i * 0.01f; 187 float thetae1 = calcEquivalentPotentialTemperature(tguess, rh, pres); 188 float thetae2 = calcEquivalentPotentialTemperature(tguess + 1, rh, 189 pres); 190 if (thetae1 == DataType.MissingData 191 || thetae2 == DataType.MissingData) { 192 return DataType.MissingData; 193 } 194 195 float correction = (thetae - thetae1) / (thetae2 - thetae1); 196 tguess += correction; 197 // ADDED: are we stuck in a loop on either side of the solution? 198 if ((oldguess2 - tguess) < 0.1 && Math.abs(correction) < 0.3) { 199 // not good enough, but need it to help find other bugs 200 tguess += 273.15f; 201 return (float) tguess; 202 } 203 if (Math.abs(correction) < conv) { 204 // return on solution 205 tguess += 273.15f; 206 return (float) tguess; 207 } 208 209 oldguess1 = tguess; 210 oldguess2 = oldguess1; 211 } 212 // failed to converge 213 return DataType.MissingData; 214 215 } 216 217 /*private static float calcRHFromDewpointAndTemperature(float dewpointC, float tempC){ 218 float saturationVaporPressure = calcSaturationVaporPressure(tempC); 219 float vaporPressure = calcVaporPressure(dewpointC); 220 return calcRHFromVaporPressures(vaporPressure, saturationVaporPressure); 221 }*/ 222 223 private static float calcRHFromVaporPressures(float vaporPressure, 224 float saturationVaporPressure) { 225 if (vaporPressure != DataType.MissingData 226 && saturationVaporPressure != DataType.MissingData) { 227 return (vaporPressure / saturationVaporPressure); 228 } 229 return DataType.MissingData; 230 } 231 232 private static float calcVaporPressure(float dewpointC) { 233 // from Bolton (MWR 1980): 234 if (dewpointC != DataType.MissingData) { 235 return (float) (6.112 * Math.exp((17.67 * dewpointC) 236 / (dewpointC + 243.5))); 237 } 238 return DataType.MissingData; 239 } 240 241 private static float calcSaturationVaporPressure(float _TempC) { 242 // from Bolton (MWR 1980): 243 if (_TempC != DataType.MissingData) { 244 return (float) (6.112f * Math.exp((17.67f * _TempC) 245 / (_TempC + 243.5f))); 246 } 247 return DataType.MissingData; 248 } 249 250 private static float calcWetBulb(float vaporPressure, 251 float satVaporPressure, float DewPointC, float _TempC, float _Pres) { 252 // uses the AWIPS method for Tw calculation at a point 253 double ed = vaporPressure; 254 double es = satVaporPressure; 255 double DewPointK = (DewPointC + 273.16); 256 float _TempK = _TempC + 273.15f; 257 258 double ew, der; 259 double de = .0001; 260 int iter = 0; 261 262 double s = ((es - ed) / (_TempK - DewPointK)); 263 double tw = (((.0006355 * _TempK * _Pres) + (DewPointK * s)) / ((.0006355 * _Pres) + s)); 264 265 if (Double.isInfinite(tw) || Double.isNaN(tw)) 266 return DataType.MissingData; 267 268 while (de >= .0001 && iter <= 10) { 269 ew = Math.exp(26.66082 - (.0091370924 * tw) - (6106.396 / tw)); 270 de = ((.0006355 * _Pres) * (_TempK - tw)) - (ew - ed); 271 der = (ew * (.0091379024 - (6106.396 / (tw * tw))) - (.0006355 * _Pres)); 272 ++iter; 273 tw = (tw - (de / der)); 274 if (Double.isInfinite(tw) || Double.isNaN(tw)) 275 return DataType.MissingData; 276 } 277 278 if (Double.isInfinite(tw) || Double.isNaN(tw)) 279 return DataType.MissingData; 280 281 return (float) tw; 282 } 283 284 private static float calcVirtualTemperature(float _TempC, float _MixingRatio) { 285 float _TempK = _TempC + 273.15f; 286 if (_TempK != DataType.MissingData 287 && _MixingRatio != DataType.MissingData) { 288 // because moist air is less dense than dry air (Newton 1717) 289 // eq is originally Hess (1959) 290 double Wtemp = _MixingRatio < 0.1 ? 0 : _MixingRatio / 1000.0; // convert 291 // to 292 // kg/kg 293 // (dimensionless) 294 295 double ret_val = _TempK * (1.0 + 1.609 * Wtemp) / (1.0 + Wtemp); 296 return (float) ret_val; 297 } 298 return DataType.MissingData; 299 300 } 301 302 private static float calcMixingRatio(float _VaporPressure, float _Pres) { 303 if (_VaporPressure != DataType.MissingData 304 && _Pres != DataType.MissingData) { 305 // Hess (1959) 306 double epsillon = .62197; 307 double ret_val = (epsillon * _VaporPressure) 308 / (_Pres - _VaporPressure); 309 // convert to g/kg 310 ret_val *= 1000.0; 311 return (float) ret_val; 312 } 313 return DataType.MissingData; 314 315 } 316 317 private static float calcPotentialTemperature(float _Pres, float _TempK) { 318 if (_Pres != DataType.MissingData && _TempK != DataType.MissingData) { 319 return (float) (_TempK * Math.pow((1000.0 / _Pres), 0.286)); 320 } 321 return DataType.MissingData; 322 } 323 324 private static float calcLclPres(float _LCLTemp, float _Pres, float _TempK) { 325 if (_LCLTemp != DataType.MissingData && _Pres != DataType.MissingData 326 && _TempK != DataType.MissingData) { 327 return (float) (_Pres * Math.pow(((_LCLTemp + 273.15) / _TempK), 328 (1 / 0.286))); 329 } 330 return DataType.MissingData; 331 } 332 333 private static float calcLclTemp(float tempC, float dewpointC) { 334 if (tempC != DataType.MissingData && dewpointC != DataType.MissingData) { 335 // From Barnes (JAM 1968, p511) 336 return dewpointC - (0.001296f * dewpointC + 0.1963f) 337 * (tempC - dewpointC); 338 } else { 339 return DataType.MissingData; 340 } 341 } 342 343 private static float calcEquivalentPotentialTemperature(float tempC, 344 float rh, float pres) { 345 float dewpoint = calcDewPoint(tempC, rh); 346 float vaporPressure = calcVaporPressure(dewpoint); 347 float mixingRatio = calcMixingRatio(vaporPressure, pres); 348 float lclTemp = calcLclTemp(tempC, dewpoint); 349 return calcEquivalentPotentialTemperature(tempC, mixingRatio, lclTemp, 350 pres); 351 } 352 353 private static float calcEquivalentPotentialTemperature(float tempC, 354 float mixingRatio, float lclTemp, float pres) { 355 float tempK = tempC + 273.16f; 356 // from Bolton (1980), equation #43 357 if (mixingRatio == DataType.MissingData 358 || tempK == DataType.MissingData 359 || lclTemp == DataType.MissingData) { 360 return DataType.MissingData; 361 } 362 float Wtemp = (mixingRatio < 0.1f) ? 0 : mixingRatio; 363 float exponent = 0.2854f * (1 - 0.00028f * Wtemp); 364 double term1 = tempK * Math.pow((1000 / pres), exponent); 365 double term2 = (3.376 / (273.15 + lclTemp)) - 0.00254; 366 double term3 = Wtemp * (1 + 0.00081 * Wtemp); 367 double thetae = term1 * Math.exp(term2 * term3); 368 if (Double.isInfinite(thetae) || Double.isNaN(thetae)) { 369 return DataType.MissingData; 370 } 371 return (float) thetae; 372 } 373 374 /** @return value of y corresponding to x */ 375 private static float interpolate(float y1, float y2, float x1, float x2, 376 float x) { 377 if (y1 == DataType.MissingData || y2 == DataType.MissingData 378 || x1 == DataType.MissingData || x2 == DataType.MissingData) { 379 return DataType.MissingData; 380 } 381 return y1 + (x - x1) * (y2 - y1) / (x2 - x1); 382 } 383 384 void fillHeightOfIsotherm(float thresh, float[][] result) { 385 for (int i = 0; i < numLat; ++i) { 386 for (int j = 0; j < numLon; ++j) { 387 result[i][j] = sfcElevation[i][j]; 388 for (int k = pressureLevels.length - 2; k >= 0; --k) { 389 if (DataType.MissingData != temperatureC[k + 1][i][j] 390 && temperatureC[k + 1][i][j] < thresh 391 && temperatureC[k][i][j] >= thresh) { 392 result[i][j] = interpolate(height[k][i][j], 393 height[k + 1][i][j], temperatureC[k][i][j], 394 temperatureC[k + 1][i][j], thresh); 395 break; 396 } 397 } 398 } 399 } 400 } 401 402 void fillHeightOfWetBulb(float threshInCelsius, float[][] result) { 403 final float threshInKelvin = 273.16f + threshInCelsius; 404 405 for (int i = 0; i < numLat; ++i) { 406 for (int j = 0; j < numLon; ++j) { 407 float lastWbTemp = DataType.MissingData; 408 for (int k = pressureLevels.length - 1; k >= 0; --k) { 409 float wbTemp = moistParcelTemp[k][i][j]; 410 // moistParcelTemperature[k][i][j]; 411 if (k != (pressureLevels.length - 1)) { 412 if (wbTemp >= threshInKelvin 413 && lastWbTemp < threshInKelvin) { 414 result[i][j] = interpolate(height[k][i][j], 415 height[k + 1][i][j], wbTemp, lastWbTemp, 416 threshInKelvin); 417 continue; 418 } 419 } 420 } 421 } 422 } 423 } 424 425 public void fillMeanWindSpeed(float botPressure, float topPressure, 426 float[][] result) { 427 for (int i = 0; i < numLat; ++i) { 428 for (int j = 0; j < numLon; ++j) { 429 float meanu = getMeanValuePressureLayer(i, j, botPressure, topPressure, this.u); 430 float meanv = getMeanValuePressureLayer(i, j, botPressure, topPressure, this.v); 431 result[i][j] = getSpeedFromUV(meanu, meanv); 432 } 433 } 434 } 435 436 static float getSpeedFromUV(float u, float v) { 437 if (u == DataType.MissingData || v == DataType.MissingData) { 438 return DataType.MissingData; 439 } else { 440 return (float) Math.sqrt(u * u + v * v); 441 } 442 } 443 444 float getMeanValueLayer(int x, int y, float botHeight, float topHeight, 445 float[][][] param) { 446 assert (param.length == pressureLevels.length); 447 448 boolean found_top = false; 449 boolean found_bot = false; 450 451 float sum_param = 0; 452 453 // FIXME: made this simple -- as a first cut, we won't bother 454 // interpolating to get the endpoints just right. Unless the 455 // model's vertical resolution is really crummy, then this 456 // probably doesn't matter, anyway! 457 // 458 for (int k = 0; k < pressureLevels.length - 1; k++) { 459 if (this.height[k][x][y] >= botHeight && !found_bot) { 460 found_bot = true; 461 } 462 if (this.height[k][x][y] >= topHeight && !found_top) { 463 if (k > 0) { 464 found_top = true; 465 } else { 466 return DataType.MissingData; 467 } 468 } 469 470 // assume that the parameter value represents the value from 471 // the bottom level to just below the next level (we aren't 472 // interpolating to get the exact values at the endpoints) 473 474 if (found_bot && !found_top) { 475 // FIXME: (a) check MISSING? (b) break loop at this point? why remain inside loop? 476 sum_param += param[k][x][y] * (height[k + 1][x][y] - height[k][x][y]); 477 } 478 } 479 if (found_bot && found_top) { 480 return (sum_param / (topHeight - botHeight)); 481 } else { 482 return DataType.MissingData; 483 } 484 } 485 486 private float getMeanValuePressureLayer(int x, int y, float botPres, 487 float topPres, float[][][] values) { 488 float botHeight = DataType.MissingData; 489 float topHeight = DataType.MissingData; 490 boolean botFound = false; 491 boolean topFound = false; 492 for (int k = 0; k < pressureLevels.length - 1; ++k) { 493 if (pressureLevels[k] >= botPres && pressureLevels[k + 1] < botPres) { 494 botHeight = interpolate(this.height[k][x][y], 495 this.height[k + 1][x][y], pressureLevels[k], 496 pressureLevels[k + 1], botPres); 497 botFound = true; 498 } else if (pressureLevels[k] >= topPres 499 && pressureLevels[k + 1] < topPres) { 500 topHeight = interpolate(this.height[k][x][y], 501 this.height[k + 1][x][y], pressureLevels[k], 502 pressureLevels[k + 1], topPres); 503 topFound = true; 504 } 505 } 506 if (botFound && topFound) { 507 float val = getMeanValueLayer(x, y, botHeight, topHeight, values); 508 return val; 509 } else { 510 return DataType.MissingData; 511 } 512 } 513 514 public void fillMeanValuePressureLayerVerticalVelocity(int botPressure, int topPressure, float[][] result) { 515 for (int i = 0; i < numLat; ++i) { 516 for (int j = 0; j < numLon; ++j) { 517 result[i][j] = getMeanValuePressureLayer(i, j, botPressure, topPressure, this.w); 518 } 519 } 520 } 521 522 public void fillMeanValuePressureLayerTemperatureC(int botPressure, int topPressure, float[][] result) { 523 for (int i = 0; i < numLat; ++i) { 524 for (int j = 0; j < numLon; ++j) { 525 result[i][j] = getMeanValuePressureLayer(i, j, botPressure, topPressure, this.temperatureC); 526 } 527 } 528 } 529 530 public void fillNumberOfUndergroundGridpoints(float[][] result) { 531 for (int i = 0; i < numLat; ++i) { 532 for (int j = 0; j < numLon; ++j) { 533 result[i][j] = nearSurfaceGridPoint[i][j]; 534 } 535 } 536 } 537 538 public void fillSurfaceRelativeHumidityPercent(float[][] result) { 539 for (int i = 0; i < numLat; ++i) { 540 for (int j = 0; j < numLon; ++j) { 541 float v = sfcRelativeHumidity[i][j]; 542 result[i][j] = (v == DataType.MissingData)? v : (v*100); 543 } 544 } 545 } 546 547 }