001 /* 002 * $Id: StormDataSource.java,v 1.1 2012/01/04 20:40:52 tommyj Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031 package edu.wisc.ssec.mcidasv.data.cyclone; 032 033 import java.rmi.RemoteException; 034 import java.util.ArrayList; 035 import java.util.Calendar; 036 import java.util.GregorianCalendar; 037 import java.util.Hashtable; 038 import java.util.List; 039 040 import ucar.unidata.data.DataCategory; 041 import ucar.unidata.data.DataChoice; 042 import ucar.unidata.data.DataSourceDescriptor; 043 import ucar.unidata.data.DataSourceImpl; 044 import ucar.unidata.data.DataUtil; 045 import ucar.unidata.data.DirectDataChoice; 046 import ucar.unidata.geoloc.Bearing; 047 import ucar.unidata.util.DateUtil; 048 import ucar.visad.Util; 049 import visad.DateTime; 050 import visad.Real; 051 import visad.RealType; 052 import visad.Unit; 053 import visad.VisADException; 054 import visad.georef.EarthLocation; 055 import visad.georef.EarthLocationLite; 056 057 /** 058 * Created by IntelliJ IDEA. User: yuanho Date: Apr 9, 2008 Time: 4:57:58 PM To 059 * change this template use File | Settings | File Templates. 060 */ 061 public abstract class StormDataSource extends DataSourceImpl { 062 063 /** _more_ */ 064 public static final int CATEGORY_DB = 0; // - disturbance, 065 066 /** _more_ */ 067 public static final int CATEGORY_TD = 1; // - tropical depression, 068 069 /** _more_ */ 070 public static final int CATEGORY_TS = 2; // - tropical storm, 071 072 /** _more_ */ 073 public static final int CATEGORY_TY = 3; // - typhoon, 074 075 /** _more_ */ 076 public static final int CATEGORY_ST = 4; // - super typhoon, 077 078 /** _more_ */ 079 public static final int CATEGORY_TC = 5; // - tropical cyclone, 080 081 /** _more_ */ 082 public static final int CATEGORY_HU = 6; // - hurricane, 083 084 /** _more_ */ 085 public static final int CATEGORY_SD = 7; // - subtropical depression, 086 087 /** _more_ */ 088 public static final int CATEGORY_SS = 8; // - subtropical storm, 089 090 /** _more_ */ 091 public static final int CATEGORY_EX = 9; // - extratropical systems, 092 093 /** _more_ */ 094 public static final int CATEGORY_IN = 10; // - inland, 095 096 /** _more_ */ 097 public static final int CATEGORY_DS = 11; // - dissipating, 098 099 /** _more_ */ 100 public static final int CATEGORY_LO = 12; // - low, 101 102 /** _more_ */ 103 public static final int CATEGORY_WV = 13; // - tropical wave, 104 105 /** _more_ */ 106 public static final int CATEGORY_ET = 14; // - extrapolated, 107 108 /** _more_ */ 109 public static final int CATEGORY_XX = 15; // - unknown. 110 111 /** _more_ */ 112 // public static StormParam PARAM_DISTANCEERROR; 113 114 /** _more_ */ 115 public static StormParam PARAM_MINPRESSURE; 116 117 /** _more_ */ 118 public static StormParam PARAM_MAXWINDSPEED_KTS; 119 120 /** _more_ */ 121 public static final int[] CATEGORY_VALUES = { CATEGORY_DB, CATEGORY_TD, 122 CATEGORY_TS, CATEGORY_TY, CATEGORY_ST, CATEGORY_TC, CATEGORY_HU, 123 CATEGORY_SD, CATEGORY_SS, CATEGORY_EX, CATEGORY_IN, CATEGORY_DS, 124 CATEGORY_LO, CATEGORY_WV, CATEGORY_ET, CATEGORY_XX }; 125 126 /** _more_ */ 127 public static final String[] CATEGORY_NAMES = { "DB", "TD", "TS", "TY", 128 "ST", "TC", "HU", "SD", "SS", "EX", "IN", "DS", "LO", "WV", "ET", 129 "XX" }; 130 131 /** _more_ */ 132 public static final String ATTR_CATEGORY = "attr.category"; 133 134 /** _more_ */ 135 public static StormParam PARAM_STORMCATEGORY; 136 137 /** _more_ */ 138 protected StormParam[] obsParams; 139 140 /** _more_ */ 141 protected StormParam[] forecastParams; 142 143 /** 144 * _more_ 145 * 146 * @throws Exception 147 * _more_ 148 */ 149 public StormDataSource() throws Exception { 150 } 151 152 /** 153 * _more_ 154 * 155 * @param descriptor 156 * _more_ 157 * @param name 158 * _more_ 159 * @param description 160 * _more_ 161 * @param properties 162 * _more_ 163 */ 164 public StormDataSource(DataSourceDescriptor descriptor, String name, 165 String description, Hashtable properties) { 166 super(descriptor, name, description, properties); 167 } 168 169 /** 170 * _more_ 171 * 172 * @return _more_ 173 */ 174 public boolean isEditable() { 175 return false; 176 } 177 178 /** 179 * _more_ 180 * 181 * @param dataChoice 182 * _more_ 183 * 184 * @return _more_ 185 */ 186 public boolean canAddCurrentName(DataChoice dataChoice) { 187 return false; 188 } 189 190 /** 191 * _more_ 192 * 193 * @param id 194 * _more_ 195 * @param alias 196 * _more_ 197 * @param unit 198 * _more_ 199 * 200 * @return _more_ 201 */ 202 protected static RealType makeRealType(String id, String alias, Unit unit) { 203 try { 204 alias = alias 205 + "[unit:" 206 + ((unit == null) ? "null" : DataUtil.cleanName(unit 207 .toString())) + "]"; 208 return ucar.visad.Util.makeRealType(id, alias, unit); 209 } catch (VisADException exc) { 210 throw new RuntimeException(exc); 211 } 212 } 213 214 /** 215 * _more_ 216 */ 217 protected final void initAfter() { 218 try { 219 incrOutstandingGetDataCalls(); 220 initializeStormData(); 221 } finally { 222 decrOutstandingGetDataCalls(); 223 } 224 } 225 226 /** 227 * _more_ 228 */ 229 protected void initializeStormData() { 230 } 231 232 /** 233 * _more_ 234 * 235 * @throws VisADException 236 * _more_ 237 */ 238 protected void initParams() throws VisADException { 239 if (PARAM_STORMCATEGORY == null) { 240 PARAM_STORMCATEGORY = new StormParam(Util.makeRealType( 241 "stormcategory", "Storm_Category", null)); 242 PARAM_MINPRESSURE = new StormParam(makeRealType("minpressure", 243 "Min_Pressure", DataUtil.parseUnit("mb"))); 244 // PARAM_DISTANCEERROR = 245 // new StormParam(Util.makeRealType("forecastlocationerror", 246 // "Distance_Error", Util.parseUnit("km")), true, 247 // false); 248 PARAM_MAXWINDSPEED_KTS = new StormParam(makeRealType( 249 "maxwindspeedkts", "Max_Windspeed", DataUtil 250 .parseUnit("kts"))); 251 252 } 253 } 254 255 /** 256 * _more_ 257 * 258 * @param name 259 * _more_ 260 * 261 * @return _more_ 262 */ 263 public int getCategory(String name) { 264 if (name == null) { 265 return CATEGORY_XX; 266 } 267 for (int i = 0; i < CATEGORY_NAMES.length; i++) { 268 if (name.equals(CATEGORY_NAMES[i])) { 269 return CATEGORY_VALUES[i]; 270 } 271 } 272 return CATEGORY_XX; 273 } 274 275 /** 276 * _more_ 277 * 278 * @return _more_ 279 */ 280 public abstract List<StormInfo> getStormInfos(); 281 282 /** 283 * _more_ 284 * 285 * @return _more_ 286 */ 287 public abstract String getId(); 288 289 /** 290 * _more_ 291 */ 292 protected void doMakeDataChoices() { 293 List cats = DataCategory.parseCategories("stormtrack", false); 294 DataChoice choice = new DirectDataChoice(this, "stormtrack", 295 "Storm Track", "Storm Track", cats, (Hashtable) null); 296 addDataChoice(choice); 297 298 } 299 300 /** 301 * Re-initialize the storm data. 302 */ 303 public void reloadData() { 304 initializeStormData(); 305 super.reloadData(); 306 } 307 308 /** 309 * _more_ 310 * 311 * @param stormInfo 312 * _more_ 313 * @param waysToUse 314 * _more_ 315 * @param obsWay 316 * _more_ 317 * 318 * @return _more_ 319 * 320 * @throws Exception 321 * _more_ 322 */ 323 public StormTrackCollection getTrackCollection(StormInfo stormInfo, 324 Hashtable<String, Boolean> waysToUse, Way obsWay) throws Exception { 325 326 try { 327 incrOutstandingGetDataCalls(); 328 return getTrackCollectionInner(stormInfo, waysToUse, obsWay); 329 } finally { 330 decrOutstandingGetDataCalls(); 331 } 332 333 } 334 335 /** 336 * _more_ 337 * 338 * @return _more_ 339 */ 340 public String getWayName() { 341 return "Way"; 342 } 343 344 /** 345 * _more_ 346 * 347 * @return _more_ 348 */ 349 public String getWaysName() { 350 return getWayName() + "s"; 351 } 352 353 /** 354 * _more_ 355 * 356 * @param stormInfo 357 * _more_ 358 * @param waysToUse 359 * _more_ 360 * @param observationWay 361 * _more_ 362 * 363 * @return _more_ 364 * 365 * @throws Exception 366 * _more_ 367 */ 368 369 public abstract StormTrackCollection getTrackCollectionInner( 370 StormInfo stormInfo, Hashtable<String, Boolean> waysToUse, 371 Way observationWay) throws Exception; 372 373 /** _more_ */ 374 private Hashtable seenWays = new Hashtable(); 375 376 /** _more_ */ 377 private List<Way> ways = new ArrayList(); 378 379 /** _more_ */ 380 private Hashtable<String, Way> wayMap = new Hashtable<String, Way>(); 381 382 /** 383 * _more_ 384 * 385 * @param way 386 * _more_ 387 * 388 * @return _more_ 389 */ 390 protected Way addWay(Way way) { 391 if (seenWays.get(way) == null) { 392 seenWays.put(way, way); 393 ways.add(way); 394 } 395 return way; 396 } 397 398 /** 399 * _more_ 400 * 401 * @param w 402 * _more_ 403 * @param name 404 * _more_ 405 * 406 * @return _more_ 407 */ 408 protected Way getWay(String w, String name) { 409 Way way = wayMap.get(w); 410 if (way == null) { 411 way = new Way(w, name); 412 wayMap.put(w, way); 413 } 414 addWay(way); 415 return way; 416 } 417 418 /** 419 * _more_ 420 * 421 * @return _more_ 422 */ 423 public List<Way> getWays() { 424 return new ArrayList<Way>(ways); 425 } 426 427 /** 428 * _more_ 429 * 430 * @param stormId 431 * _more_ 432 * 433 * @return _more_ 434 */ 435 public StormInfo getStormInfo(String stormId) { 436 List<StormInfo> stormInfos = getStormInfos(); 437 for (StormInfo sInfo : stormInfos) { 438 if (sInfo.getStormId().equals(stormId)) { 439 return sInfo; 440 } 441 } 442 return null; 443 } 444 445 /** 446 * _more_ 447 * 448 * @param dttm 449 * _more_ 450 * 451 * @return _more_ 452 * 453 * @throws VisADException 454 * _more_ 455 */ 456 public static int getYear(DateTime dttm) throws VisADException { 457 GregorianCalendar cal = new GregorianCalendar(DateUtil.TIMEZONE_GMT); 458 cal.setTime(ucar.visad.Util.makeDate(dttm)); 459 return cal.get(Calendar.YEAR); 460 } 461 462 /** 463 * _more_ 464 * 465 * @param obsTrack 466 * _more_ 467 * @param fctTrack 468 * _more_ 469 * 470 * @throws VisADException 471 * _more_ 472 */ 473 public static void addDistanceError(StormTrack obsTrack, StormTrack fctTrack) 474 throws VisADException { 475 List<StormTrackPoint> obsTrackPoints = obsTrack.getTrackPoints(); 476 List<StormTrackPoint> fctTrackPoints = fctTrack.getTrackPoints(); 477 478 for (StormTrackPoint stp : fctTrackPoints) { 479 DateTime dt = stp.getTime(); 480 // StormTrackPoint stpObs = getClosestPoint(obsTrackPoints, dt); 481 // double der = getDistance(stpObs, stp); 482 // stp.addAttribute(PARAM_DISTANCEERROR.getReal(der)); 483 } 484 485 } 486 487 /** 488 * _more_ 489 * 490 * @param obsTrack 491 * _more_ 492 * @param fctTrack 493 * _more_ 494 * @param param 495 * _more_ 496 * 497 * @return _more_ 498 * 499 * @throws RemoteException 500 * _more_ 501 * @throws VisADException 502 * _more_ 503 */ 504 public static StormTrack difference(StormTrack obsTrack, 505 StormTrack fctTrack, StormParam param) throws VisADException, 506 RemoteException { 507 List<StormTrackPoint> obsTrackPoints = obsTrack.getTrackPoints(); 508 List<StormTrackPoint> fctTrackPoints = fctTrack.getTrackPoints(); 509 List<StormTrackPoint> diffPoints = new ArrayList<StormTrackPoint>(); 510 511 for (StormTrackPoint forecastPoint : fctTrackPoints) { 512 Real forecastValue = forecastPoint.getAttribute(param); 513 if (forecastValue == null) { 514 continue; 515 } 516 DateTime forecastDttm = forecastPoint.getTime(); 517 StormTrackPoint[] range = getClosestPointRange(obsTrackPoints, 518 forecastDttm); 519 if (range == null) { 520 continue; 521 } 522 Real obsValue = null; 523 if (range.length == 1) { 524 // exact match: 525 obsValue = range[0].getAttribute(param); 526 } else { 527 // Interpolate between the two points 528 Real v1 = range[0].getAttribute(param); 529 Real v2 = range[1].getAttribute(param); 530 if ((v1 == null) || (v2 == null)) { 531 continue; 532 } 533 DateTime t1 = range[0].getTime(); 534 DateTime t2 = range[1].getTime(); 535 double percent = forecastDttm.getValue() - t1.getValue() 536 / (t2.getValue() - t1.getValue()); 537 538 double interpolatedValue = v2.getValue() + percent 539 * (v2.getValue() - v1.getValue()); 540 obsValue = v1.cloneButValue(interpolatedValue); 541 System.err.println("interp %:" + percent + " v:" + obsValue 542 + " v1:" + v1 + " v2:" + v2 + "\n\tt1:" + t1 + " t2:" 543 + t2); 544 } 545 546 if (obsValue == null) { 547 continue; 548 } 549 550 Real difference = (Real) forecastValue.__sub__(obsValue); 551 StormTrackPoint newStormTrackPoint = new StormTrackPoint( 552 forecastPoint.getLocation(), forecastDttm, forecastPoint 553 .getForecastHour(), new ArrayList<Real>()); 554 newStormTrackPoint.addAttribute(difference); 555 diffPoints.add(newStormTrackPoint); 556 } 557 if (diffPoints.size() == 0) { 558 return null; 559 } 560 return new StormTrack(fctTrack.getStormInfo(), fctTrack.getWay(), 561 diffPoints, null); 562 } 563 564 /** 565 * _more_ 566 * 567 * @param aList 568 * _more_ 569 * @param dt 570 * _more_ 571 * 572 * @return _more_ 573 */ 574 public static StormTrackPoint[] getClosestPointRange( 575 List<StormTrackPoint> aList, DateTime dt) { 576 double timeToLookFor = dt.getValue(); 577 int numPoints = aList.size(); 578 double lastTime = -1; 579 580 for (int i = 0; i < numPoints; i++) { 581 StormTrackPoint stp = aList.get(i); 582 double currentTime = stp.getTime().getValue(); 583 if (timeToLookFor == currentTime) { 584 return new StormTrackPoint[] { stp }; 585 } 586 if (timeToLookFor < currentTime) { 587 if (i == 0) { 588 return null; 589 } 590 if (timeToLookFor > lastTime) { 591 return new StormTrackPoint[] { aList.get(i - 1), stp }; 592 } 593 } 594 lastTime = currentTime; 595 } 596 return null; 597 } 598 599 /** 600 * _more_ 601 * 602 * @param aList 603 * _more_ 604 * @param dt 605 * _more_ 606 * 607 * @return _more_ 608 */ 609 public static StormTrackPoint getClosestPoint(List<StormTrackPoint> aList, 610 DateTime dt) { 611 612 int numPoints = aList.size(); 613 StormTrackPoint stp1 = aList.get(0); 614 StormTrackPoint stp2 = aList.get(numPoints - 1); 615 616 double pValue = dt.getValue(); 617 double minDiffLeft = 200000; 618 double minDiffRight = 200000; 619 620 for (int i = 0; i < numPoints; i++) { 621 StormTrackPoint stp11 = aList.get(i); 622 StormTrackPoint stp21 = aList.get(numPoints - i - 1); 623 624 double p1Value = stp11.getTime().getValue(); 625 double p2Value = stp21.getTime().getValue(); 626 double diff1 = Math.abs(p1Value - pValue); 627 double diff2 = Math.abs(p2Value - pValue); 628 629 if ((pValue >= p1Value) && (diff1 < minDiffRight)) { 630 if (pValue == p1Value) { 631 return stp11; 632 } 633 stp1 = stp11; 634 minDiffRight = diff1; 635 636 } 637 638 if ((pValue <= p2Value) && (diff2 < minDiffLeft)) { 639 if (pValue == p2Value) { 640 return stp21; 641 } 642 stp2 = stp21; 643 minDiffLeft = diff2; 644 } 645 646 } 647 648 double diff = minDiffLeft + minDiffRight; 649 EarthLocation el1 = stp1.getLocation(); 650 EarthLocation el2 = stp1.getLocation(); 651 652 double lat = ((diff - minDiffLeft) * el1.getLatitude().getValue() + (diff - minDiffRight) 653 * el2.getLatitude().getValue()) 654 / diff; 655 double lon = ((diff - minDiffLeft) * el1.getLongitude().getValue() + (diff - minDiffRight) 656 * el2.getLongitude().getValue()) 657 / diff; 658 659 EarthLocation el = new EarthLocationLite(new Real(RealType.Latitude, 660 lat), new Real(RealType.Longitude, lon), null); 661 662 return new StormTrackPoint(el, dt, 0, null); 663 664 } 665 666 /** 667 * _more_ 668 * 669 * @return _more_ 670 */ 671 public boolean getIsObservationWayChangeable() { 672 return false; 673 } 674 675 /** 676 * _more_ 677 * 678 * @return _more_ 679 */ 680 public Way getDefaultObservationWay() { 681 return null; 682 } 683 684 /** 685 * _more_ 686 * 687 * @param p1 688 * _more_ 689 * @param p2 690 * _more_ 691 * 692 * @return _more_ 693 */ 694 public static double getDistance(StormTrackPoint p1, StormTrackPoint p2) { 695 696 EarthLocation el1 = p1.getLocation(); 697 EarthLocation el2 = p2.getLocation(); 698 699 Bearing b = Bearing.calculateBearing(el1.getLatitude().getValue(), el1 700 .getLongitude().getValue(), el2.getLatitude().getValue(), el2 701 .getLongitude().getValue(), null); 702 return b.getDistance(); 703 704 } 705 }