001 /** 002 * 003 */ 004 package org.wdssii.nse; 005 006 import java.util.ArrayList; 007 import java.util.Collections; 008 import java.util.Comparator; 009 import java.util.Date; 010 import java.util.List; 011 012 import org.apache.commons.logging.Log; 013 import org.apache.commons.logging.LogFactory; 014 import org.wdssii.core.Algorithm; 015 import org.wdssii.core.BuilderFactory; 016 import org.wdssii.core.DataEncoder; 017 import org.wdssii.core.DataType; 018 import org.wdssii.core.IndexRecord; 019 import org.wdssii.core.LatLonGrid; 020 import org.wdssii.core.Location; 021 import org.wdssii.core.UdUnits; 022 023 import ucar.units.Unit; 024 025 /** 026 * 027 * Near-storm environment algorithm. 028 * 029 * @author lakshman 030 * 031 */ 032 public class NseAlgorithm extends Algorithm { 033 private static Log log = LogFactory.getLog(NseAlgorithm.class); 034 035 private String heightProductName = "HGT-ISBL"; 036 private String temperatureProductName = "TMP-ISBL"; 037 private String relativeHumidityProductName = "RH-ISBL"; 038 private String gridRelativeUWindProductName = "UGRD-ISBL"; 039 private String gridRelativeVWindProductName = "VGRD-ISBL"; 040 private String verticalVelocityProductName = "VVEL-ISBL"; 041 private String terrainElevationProductName = "HGT-SFC"; 042 private String mslPressureProductName = "MSLMA-MSL"; 043 private String sfcGridRelativeUWindProductName = "UGRD-HTGL"; 044 private String sfcGridRelativeVWindProductName = "VGRD-HTGL"; 045 private String sfcPressureProductName = "PRES-SFC"; 046 private String sfcTemperatureProductName = "TMP-HTGL"; 047 private String sfcDewPointDepression = "DEPR-HTGL"; 048 private String subTypeSuffix = "_analysis"; 049 private int numPressureLevels = 31; 050 051 public static void main(String[] args) throws Exception { 052 NseAlgorithm alg = new NseAlgorithm(); 053 alg.setup(args); 054 alg.init(); 055 alg.execute(); 056 } 057 058 public static class InProduct { 059 private String productName; 060 private Unit unit; 061 private List<LatLonGrid> llgrids = new ArrayList<LatLonGrid>(); 062 063 public InProduct(String productName, String unit) { 064 this.productName = productName; 065 if (unit != null) { 066 this.unit = UdUnits.parse(unit); 067 } 068 } 069 070 public void update(LatLonGrid grid) { 071 if (this.getTime().before(grid.getTime())) { 072 log.info("New " + productName 073 + " grid received ... deleting older grids"); 074 llgrids.clear(); 075 } 076 if (unit != null) { 077 grid.convertToUnit(unit); 078 } 079 llgrids.add(grid); 080 log.info("Received " + llgrids.size() + " products for " 081 + productName); 082 } 083 084 public int getNumGrids() { 085 return llgrids.size(); 086 } 087 088 public Date getTime() { 089 if (llgrids.size() == 0) { 090 return new Date(0); 091 } 092 return llgrids.get(0).getTime(); 093 } 094 095 public void sortByPressureLevel() { 096 Collections.sort(llgrids, new Comparator<LatLonGrid>() { 097 @Override 098 public int compare(LatLonGrid o1, LatLonGrid o2) { 099 Double p1 = new Double((String) o1 100 .getAttribute("PressureLevel")); 101 Double p2 = new Double((String) o2 102 .getAttribute("PressureLevel")); 103 // higher pressures first 104 return p2.compareTo(p1); 105 } 106 }); 107 } 108 109 public float getPressureLevel(int index){ 110 return Float.valueOf((String) llgrids.get(index).getAttribute("PressureLevel")); 111 } 112 } 113 114 private InProduct[] in3DProducts; 115 private InProduct[] inSfcProducts; 116 117 private void init() { 118 in3DProducts = new InProduct[] { 119 new InProduct(heightProductName, "gpm"), 120 new InProduct(temperatureProductName, "degreeC"), 121 new InProduct(relativeHumidityProductName, null), // will get changed to dewpoint 122 new InProduct(gridRelativeUWindProductName, "MetersPerSecond"), // will get changed to u 123 new InProduct(gridRelativeVWindProductName, "MetersPerSecond"), // will get changed to v 124 new InProduct(verticalVelocityProductName, "MetersPerSecond") }; 125 inSfcProducts = new InProduct[] { 126 new InProduct(terrainElevationProductName, "m"), 127 new InProduct(mslPressureProductName, "mb"), 128 new InProduct(sfcGridRelativeUWindProductName, "MetersPerSecond"), // will get changed to u 129 new InProduct(sfcGridRelativeVWindProductName, "MetersPerSecond"), // will get changed to v 130 new InProduct(sfcPressureProductName, "mb"), 131 new InProduct(sfcTemperatureProductName, "degreeC"), 132 new InProduct(sfcDewPointDepression, "degreeC"), }; // will get changed to dewpoint 133 } 134 135 private void runNSE(){ 136 // set up pressure levels 137 float[] pressureLevels = new float[numPressureLevels]; 138 for (int i=0; i < numPressureLevels; ++i){ 139 float ht = in3DProducts[0].getPressureLevel(i); 140 float temp = in3DProducts[1].getPressureLevel(i); 141 float dewp = in3DProducts[2].getPressureLevel(i); 142 float u = in3DProducts[3].getPressureLevel(i); 143 float v = in3DProducts[4].getPressureLevel(i); 144 float w = in3DProducts[5].getPressureLevel(i); 145 if (ht != temp || dewp != temp || u != temp || v != temp || w != temp){ 146 log.error("The pressure levels in the grids do not match ..."); 147 return; 148 } 149 pressureLevels[i] = ht; 150 } 151 152 // set up NSE profile 153 NSEProfile profile = new NSEProfile(pressureLevels); 154 profile.sfcElevation = inSfcProducts[0].llgrids.get(0).getValues(); 155 profile.mslPressure = inSfcProducts[1].llgrids.get(0).getValues(); 156 profile.sfcUWind = inSfcProducts[2].llgrids.get(0).getValues(); 157 profile.sfcVWind = inSfcProducts[3].llgrids.get(0).getValues(); 158 profile.sfcPressure = inSfcProducts[4].llgrids.get(0).getValues(); 159 profile.sfcTemperature = inSfcProducts[5].llgrids.get(0).getValues(); 160 profile.sfcDewPoint = inSfcProducts[6].llgrids.get(0).getValues(); 161 162 for (int i=0; i < numPressureLevels; ++i){ 163 profile.height[i] = in3DProducts[0].llgrids.get(i).getValues(); 164 profile.temperatureC[i] = in3DProducts[1].llgrids.get(i).getValues(); 165 profile.dewpoint[i] = in3DProducts[2].llgrids.get(i).getValues(); 166 profile.u[i] = in3DProducts[3].llgrids.get(i).getValues(); 167 profile.v[i] = in3DProducts[4].llgrids.get(i).getValues(); 168 profile.w[i] = in3DProducts[5].llgrids.get(i).getValues(); 169 } 170 profile.finishInit(); 171 172 // write out isotherm products 173 LatLonGrid output = new LatLonGrid(inSfcProducts[0].llgrids.get(0)); 174 float[][] temp2DArray = output.getValues(); 175 176 profile.fillHeightOfIsotherm(0, temp2DArray); 177 writeOutputGrid(output, temp2DArray, "HeightOf0C", "nseanalysis", "m"); 178 179 profile.fillHeightOfIsotherm(-10, temp2DArray); 180 writeOutputGrid(output, temp2DArray, "HeightOf-10C", "nseanalysis", "m"); 181 182 profile.fillHeightOfIsotherm(-20, temp2DArray); 183 writeOutputGrid(output, temp2DArray, "HeightOf-20C", "nseanalysis", "m"); 184 185 profile.fillHeightOfWetBulb(0, temp2DArray); 186 writeOutputGrid(output, temp2DArray, "HeightOfWetBulbZero", "nseanalysis", "m"); 187 188 profile.fillHeightOfWetBulb(4, temp2DArray); 189 writeOutputGrid(output, temp2DArray, "HeightOfWetBulbFour", "nseanalysis", "m"); 190 191 profile.fillMeanWindSpeed(500, 300, temp2DArray); 192 writeOutputGrid(output, temp2DArray, "WindSpeedMean500-300mb", "nseanalysis", "m/s"); 193 194 profile.fillMeanValuePressureLayerVerticalVelocity(850, 500, temp2DArray); 195 writeOutputGrid(output, temp2DArray, "VerticalVelocityMean_850-500mb", "nseanalysis", "dimensionless"); 196 197 profile.fillMeanValuePressureLayerTemperatureC(500, 400, temp2DArray); 198 writeOutputGrid(output, temp2DArray, "TemperatureMean_500-400mb", "nseanalysis", "degreeC"); 199 200 profile.fillNumberOfUndergroundGridpoints(temp2DArray); 201 writeOutputGrid(output, temp2DArray, "NumUndergroundGridpoints", "nseanalysis", "dimensionless"); 202 203 profile.fillSurfaceRelativeHumidityPercent(temp2DArray); 204 writeOutputGrid(output, temp2DArray, "SfcRH", "nseanalysis", "%"); 205 206 writeOutputGrid(output, profile.sfcDewPoint, "SfcDewPoint", "nseanalysis", "degreeC"); 207 208 writeOutputGrid(output, profile.sfcVirtualTemperature, "SfcTv", "nseanalysis", "degreeC"); 209 210 writeOutputGrid(output, profile.sfcThetaE, "SfcThetaE", "nseanalysis", "degreeK"); 211 212 writeOutputGrid(output, profile.sfcTheta, "SfcTheta", "nseanalysis", "degreeK"); 213 214 writeOutputGrid(output, profile.sfcMixingRatio, "SfcMixingRatio", "nseanalysis", "GramsPerKilogram"); 215 216 writeOutputGrid(output, profile.sfcLclTemperature, "SfcLCLTemp", "nseanalysis", "degreeC"); 217 218 writeOutputGrid(output, profile.sfcLclPressure, "SfcLCLPress", "nseanalysis", "mb"); 219 220 writeOutputGrid(output, profile.sfcWetBulbTemperatureK, "SfcWetBulb", "nseanalysis", "degreeK"); 221 } 222 223 private void writeOutputGrid(LatLonGrid output, float[][] values, String typeName, String subType, String unit) { 224 float[][] orig = output.getValues(); 225 output.setValues(values); 226 try { 227 output.setTypeName(typeName); 228 output.setAttribute("SubType", subType); 229 output.setAttribute("Unit", unit); 230 DataEncoder.writeDataAndNotify(output, getOutputDir()); 231 } finally { 232 output.setValues(orig); 233 } 234 } 235 236 private boolean updateProduct(IndexRecord rec) { 237 String dataType = rec.getDataType(); 238 for (InProduct prod : in3DProducts) { 239 if (prod.productName.equals(dataType)) { 240 LatLonGrid grid = (LatLonGrid) BuilderFactory 241 .createDataType(rec); 242 prod.update(grid); 243 return true; 244 } 245 } 246 for (InProduct prod : inSfcProducts) { 247 if (prod.productName.equals(dataType)) { 248 LatLonGrid grid = (LatLonGrid) BuilderFactory 249 .createDataType(rec); 250 prod.update(grid); 251 return true; 252 } 253 } 254 log.debug("Not interested in " + dataType); 255 return false; 256 } 257 258 private boolean suffixMatches(IndexRecord rec) { 259 String subType = rec.getSubType(); 260 if (!subType.endsWith(subTypeSuffix)) { 261 if (log.isDebugEnabled()) { 262 String dataType = rec.getDataType(); 263 log.debug("Ignoring " + dataType + ":" + subType 264 + " since the subType does not end with " 265 + subTypeSuffix); 266 } 267 return false; 268 } 269 return true; 270 } 271 272 private boolean haveAllProducts() { 273 // do we have all the grids we need? 274 for (InProduct prod : in3DProducts) { 275 if (prod.llgrids.size() != numPressureLevels) { 276 log.debug("Have received only " + prod.llgrids.size() + " of " 277 + numPressureLevels + " pressure levels for " 278 + prod.productName); 279 return false; 280 } 281 } 282 for (InProduct prod : inSfcProducts) { 283 if (prod.llgrids.size() != 1) { 284 log.debug("Have not yet received " + prod.productName); 285 return false; 286 } 287 } 288 log.info("Have received all the products required."); 289 290 return true; 291 } 292 293 private void clearAllProducts() { 294 for (InProduct prod : in3DProducts) { 295 prod.llgrids.clear(); 296 } 297 for (InProduct prod : inSfcProducts) { 298 prod.llgrids.clear(); 299 } 300 } 301 302 private void prepareWindProducts(List<LatLonGrid> ugrid, 303 List<LatLonGrid> vgrid) { 304 log.info("Converting grid-relative winds in " 305 + ugrid.get(0).getTypeName() + " north-relative"); 306 log.info("Converting grid-relative winds in " 307 + vgrid.get(0).getTypeName() + " north-relative"); 308 309 // Here are some constants for the Lambert Conformal proejection: 310 final double WindRotationConstant = 0.422618; 311 final double XMeridian = -95.0; 312 313 Location nwcorner = ugrid.get(0).getLocation(); 314 double w_lat = nwcorner.getLongitude(); 315 float lonres = ugrid.get(0).getDeltaLon(); 316 int numLat = ugrid.get(0).getNumLat(); 317 int numLon = ugrid.get(0).getNumLon(); 318 for (int k = 0; k < ugrid.size(); ++k) { 319 float[][] u = ugrid.get(k).getValues(); 320 float[][] v = vgrid.get(k).getValues(); 321 for (int i = 0; i < numLat; ++i) { 322 for (int j = 0; j < numLon; ++j) { 323 if (u[i][j] == DataType.MissingData 324 || v[i][j] == DataType.MissingData) { 325 u[i][j] = v[i][j] = DataType.MissingData; 326 } else { 327 double ang = WindRotationConstant 328 * (w_lat + j * lonres - XMeridian) * 0.017453; 329 double sinx = Math.sin(ang); 330 double cosx = Math.cos(ang); 331 double ug = u[i][j]; 332 double vg = v[i][j]; 333 double u_new = cosx * ug + sinx * vg; 334 double v_new = -sinx * ug + cosx * vg; 335 // don't need a decimal precision more than 2 places 336 int u_trunc = (int) (u_new * 100); 337 int v_trunc = (int) (v_new * 100); 338 u[i][j] = u_trunc * 0.01f; 339 v[i][j] = v_trunc * 0.01f; 340 } 341 342 } 343 } 344 ugrid.get(k).setTypeName("U"); 345 vgrid.get(k).setTypeName("V"); 346 } 347 } 348 349 public static float calcDewpointFromRH(float temp, float rh) { 350 float dewp = DataType.MissingData; 351 352 if ((temp != DataType.MissingData) && (rh != DataType.MissingData)) { 353 float RH = rh * 0.01f; 354 // from Bolton (MWR 1980): 355 float es = 6.112f * (float) Math.exp((17.67f * temp) / (temp + 243.5f)); 356 float e = es * RH; 357 dewp = (243.5f * (float) Math.log(e) - 440.8f) / (19.48f - (float) Math.log(e)); 358 359 } 360 361 return dewp; 362 363 } 364 365 private void prepareRelativeHumidity(List<LatLonGrid> tempGrids, 366 List<LatLonGrid> rhGrids) { 367 log.info("Converting relative humidity in " 368 + rhGrids.get(0).getTypeName() 369 + " to dewpoint based on temperature from " 370 + tempGrids.get(0).getTypeName()); 371 int numLat = rhGrids.get(0).getNumLat(); 372 int numLon = rhGrids.get(0).getNumLon(); 373 for (int k = 0; k < rhGrids.size(); ++k) { 374 float[][] rhvalues = rhGrids.get(k).getValues(); 375 float[][] tempvalues = tempGrids.get(k).getValues(); 376 for (int i = 0; i < numLat; ++i) { 377 for (int j = 0; j < numLon; ++j) { 378 float rh = rhvalues[i][j]; 379 float temp = tempvalues[i][j]; 380 rhvalues[i][j] = calcDewpointFromRH(temp, rh); 381 } 382 } 383 rhGrids.get(k).setTypeName("DewPoint"); 384 } 385 } 386 387 private void prepareDewPointDepression(List<LatLonGrid> tempGrids, 388 List<LatLonGrid> ddGrids) { 389 log.info("Converting dewpoint depression in " 390 + ddGrids.get(0).getTypeName() 391 + " to dewpoint based on temperature from " 392 + tempGrids.get(0).getTypeName()); 393 int numLat = ddGrids.get(0).getNumLat(); 394 int numLon = ddGrids.get(0).getNumLon(); 395 for (int k = 0; k < ddGrids.size(); ++k) { 396 float[][] ddvalues = ddGrids.get(k).getValues(); 397 float[][] tempvalues = tempGrids.get(k).getValues(); 398 for (int i = 0; i < numLat; ++i) { 399 for (int j = 0; j < numLon; ++j) { 400 float dd = ddvalues[i][j]; 401 float temp = tempvalues[i][j]; 402 ddvalues[i][j] = (dd != DataType.MissingData && temp != DataType.MissingData)? DataType.MissingData : (temp-dd); 403 } 404 } 405 ddGrids.get(k).setTypeName("DewPoint"); 406 } 407 } 408 409 @Override 410 public void handleRecord(IndexRecord rec) { 411 if (suffixMatches(rec) && updateProduct(rec) && haveAllProducts()) { 412 try { 413 for (InProduct prod : in3DProducts) { 414 prod.sortByPressureLevel(); 415 } 416 prepareWindProducts(in3DProducts[3].llgrids, 417 in3DProducts[4].llgrids); 418 prepareWindProducts(inSfcProducts[2].llgrids, 419 inSfcProducts[3].llgrids); 420 prepareRelativeHumidity(in3DProducts[1].llgrids, 421 in3DProducts[2].llgrids); 422 prepareDewPointDepression(inSfcProducts[5].llgrids, 423 inSfcProducts[6].llgrids); 424 425 runNSE(); 426 427 } finally { 428 clearAllProducts(); 429 } 430 } 431 } 432 }