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 }