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 }