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