diff --git a/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/bundles/ncepHydro/ConvectiveWatchPlot.xml b/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/bundles/ncepHydro/ConvectiveWatchPlot.xml new file mode 100644 index 0000000000..01d6f48442 --- /dev/null +++ b/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/bundles/ncepHydro/ConvectiveWatchPlot.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/menus/ncepHydro/spc/baseSPC.xml b/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/menus/ncepHydro/spc/baseSPC.xml index 535f7795f2..b3ed96c9c5 100644 --- a/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/menus/ncepHydro/spc/baseSPC.xml +++ b/cave/com.raytheon.uf.viz.d2d.ui.ncephydro/localization/menus/ncepHydro/spc/baseSPC.xml @@ -20,8 +20,8 @@ --> - + diff --git a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WWAResourceData.java b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WWAResourceData.java index ac3b495abc..ade59795fd 100644 --- a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WWAResourceData.java +++ b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WWAResourceData.java @@ -42,6 +42,7 @@ import com.raytheon.viz.core.mode.CAVEMode; * May 3, 2011 jsanchez Initial creation * Oct 25, 2013 2249 rferrel getAvailableTimes always returns a non-empty list. * Apr 28, 2014 DR 17310 D. Friedman Handle null VTEC fields. + * Aug 28, 2014 ASM #15682 D. Friedman Refactor for WouWcnWatchesResourceData. * * * @@ -98,13 +99,13 @@ public class WWAResourceData extends AbstractRequestableResourceData { @Override public DataTime[] getAvailableTimes() throws VizException { - DataTime[] available = getAvailableTimes(getMetadataMap(), + DataTime[] available = getAvailableWarningTimes(getMetadataMap(), getBinOffset()); return available; } - public static DataTime[] getAvailableTimes( + public DataTime[] getAvailableWarningTimes( Map constraintMap, BinOffset binOffset) throws VizException { DbQueryResponse response = null; @@ -116,8 +117,9 @@ public class WWAResourceData extends AbstractRequestableResourceData { String etn = "etn"; String phensig = "phensig"; String act = "act"; + String pil = "pil"; request.addFields(new String[] { startTimeField, endTimeField, act, - etn, phensig }); + etn, phensig, pil }); response = (DbQueryResponse) ThriftClient.sendRequest(request); if (response.getResults() == null) { @@ -137,7 +139,10 @@ public class WWAResourceData extends AbstractRequestableResourceData { warnRec.setAct((String) map.get(act)); warnRec.setPhensig((String) map.get(phensig)); warnRec.setEtn((String) map.get(etn)); - warnings.add(warnRec); + warnRec.setPil((String) map.get(pil)); + if (isRecordTimeImportant(warnRec)) { + warnings.add(warnRec); + } } RequestConstraint phenSig = constraintMap.get("phensig"); @@ -165,6 +170,10 @@ public class WWAResourceData extends AbstractRequestableResourceData { return availableTimes; } + protected boolean isRecordTimeImportant(AbstractWarningRecord warnRec) { + return true; + } + private static TreeSet getWarningStartTimes( ArrayList warnings) { /* diff --git a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WatchesResource.java b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WatchesResource.java index 1494ac5034..85d5bf47b2 100644 --- a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WatchesResource.java +++ b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WatchesResource.java @@ -290,7 +290,7 @@ public class WatchesResource extends AbstractWWAResource { } } - private void setGeometry(AbstractWarningRecord record) { + protected void setGeometry(AbstractWarningRecord record) { List county = new ArrayList(); List marinezone = new ArrayList(); List geometries = new ArrayList(); diff --git a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResource.java b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResource.java new file mode 100644 index 0000000000..a6374b1e0d --- /dev/null +++ b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResource.java @@ -0,0 +1,429 @@ +package com.raytheon.viz.warnings.rsc; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +import com.raytheon.uf.common.dataplugin.warning.AbstractWarningRecord; +import com.raytheon.uf.common.dataplugin.warning.WarningRecord.WarningAction; +import com.raytheon.uf.common.time.ISimulatedTimeChangeListener; +import com.raytheon.uf.common.time.SimulatedTime; +import com.raytheon.uf.viz.core.IGraphicsTarget; +import com.raytheon.uf.viz.core.drawables.FillPatterns; +import com.raytheon.uf.viz.core.drawables.IShadedShape; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.rsc.LoadProperties; +import com.raytheon.viz.core.rsc.jts.JTSCompiler; +import com.raytheon.viz.core.rsc.jts.JTSCompiler.PointStyle; +import com.vividsolutions.jts.geom.Geometry; + +/** + * Displays WOUs updated by WCNs + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * 2014-08-28   ASM #15682 D. Friemdan Initial creation
+ * 
+ * + */ +public class WouWcnWatchesResource extends WatchesResource implements ISimulatedTimeChangeListener { + + private static Timer timer; + + private TimerTask timerTask; + + // If this is changed to use the maps database, could probably be static + private Map> cwaUgcMap = new HashMap>(); + + static final ThreadLocal sdf = new ThreadLocal() { + @Override protected SimpleDateFormat initialValue() { + return new SimpleDateFormat("yyyyMMddHHmm"); + } + }; + + public WouWcnWatchesResource(WWAResourceData data, LoadProperties props) { + super(data, props); + comparator = WouWcnWatchesComparator.getInstance(); + resourceName = "Watches"; + } + + private AbstractWarningRecord getPreviousRecordForEvent(AbstractWarningRecord rec) { + String phenSig = rec.getPhensig(); + String etn = rec.getEtn(); + if (phenSig == null || etn == null) + return null; + AbstractWarningRecord best = null; + for (WarningEntry e : entryMap.values()) { + if (!phenSig.equals(e.record.getPhensig()) || + !etn.equals(e.record.getEtn())) + continue; + + if (best == null || WouWcnWatchesComparator.getInstance(). + compare(best, e.record) < 0) { + best = e.record; + } + } + return best; + } + + private Set maskCwaUgcs(Set ugcs, AbstractWarningRecord rec) { + Set cwaUgcs = getUgcsForCwa(rec.getXxxid()); + if (cwaUgcs != null) { + HashSet result = new HashSet(ugcs); + result.removeAll(cwaUgcs); + return result; + } else { + return ugcs; + } + } + + private Set getUgcsForCwa(String cwa) { + return cwaUgcMap.get(cwa.toUpperCase()); + } + + private Set safe(Set set) { + return set != null ? set : new HashSet(); + } + + @Override + protected void updateDisplay(IGraphicsTarget target) throws VizException { + if (recordsToLoad.isEmpty()) + return; + + List mergedWatches = mergeWatches(recordsToLoad); + for (AbstractWarningRecord watchRec : mergedWatches) { + /* If these things are missing, we can't do anything with the warning. */ + if (watchRec.getPhensig() == null || watchRec.getEtn() == null || + watchRec.getIssueTime() == null || watchRec.getStartTime() == null || + watchRec.getEndTime() == null || watchRec.getXxxid() == null || + watchRec.getWmoid() == null || watchRec.getAct() == null) { + continue; + } + + WarningAction watchAct = WarningAction.valueOf(watchRec.getAct()); + + AbstractWarningRecord createShape = null; + boolean isWOU = "WOU".equals(watchRec.getPil()); + + AbstractWarningRecord prevRec = getPreviousRecordForEvent(watchRec); + Set prevUgcs = new HashSet(safe( + prevRec != null ? prevRec.getUgcZones() : null)); + Set newUgcs = null; + + if (watchAct == WarningAction.NEW) { + if (isWOU) { + createShape = watchRec; + } else { + noteCwaUgcs(watchRec); + // As per requirements, we do not create frames for these. + } + } else if (watchAct == WarningAction.CON && isWOU) { + // As per requirements, we do not create frames for these. + } else if (watchAct == WarningAction.CON) { + /* No need to do anything because we really only care about + * the segments paired with the CON. + */ + } else if (watchAct == WarningAction.CAN) { + /* Not really expecting this for a WOU, but shouldn't cause + * a problem if there is one. + */ + newUgcs = prevUgcs; + newUgcs.removeAll(safe(watchRec.getUgcZones())); + createShape = watchRec; + } else if (watchAct == WarningAction.EXA || watchAct == WarningAction.EXB) { + if (!isWOU) { + noteCwaUgcs(watchRec); + } + newUgcs = prevUgcs; + newUgcs.addAll(safe(watchRec.getUgcZones())); + createShape = watchRec; + } else if (watchAct == WarningAction.EXP) { + if (isWOU) { + if (prevRec != null) { + if (! prevRec.getEndTime().equals(watchRec.getEndTime())) { + prevRec.setEndTime(watchRec.getEndTime()); + } + } + /* + * Ideally we do not need to create a shape, but if we do + * not and time matching creates a frame for an EXP that is + * issued before the expiration time, the warning would show + * as still active on that frame. + */ + newUgcs = new HashSet(); + createShape = watchRec; + } else { + newUgcs = maskCwaUgcs(prevUgcs, watchRec); + createShape = watchRec; + } + } + if (watchAct == WarningAction.EXT || watchAct == WarningAction.EXB) { + /* This resource does not handle different expiration times + * for different UGCs. + * + * Also assuming this does not add/remove UGCs. + */ + if (prevRec != null && watchRec.getEndTime() != null) { + if (isWOU && watchRec.getUgcZones() != null && watchRec.getUgcZones().isEmpty()) { + /* + * This probably does not actually happen, but this + * is the only way we can support shortening the + * expiration time with the current design. + */ + prevRec.setEndTime(watchRec.getEndTime()); + } else { + if (prevRec.getEndTime().before(watchRec.getEndTime())) { + prevRec.setEndTime(watchRec.getEndTime()); + } + } + } + } + + if (createShape != null) { + if (newUgcs != null) + createShape.setUgcZones(newUgcs); + else if (createShape.getUgcZones() == null) + createShape.setUgcZones(new HashSet()); + insertShape(target, createShape); + } + } + + recordsToLoad.clear(); + scheduleNextTime(); + } + + + @Override + protected void initShape(IGraphicsTarget target, + AbstractWarningRecord record) throws VizException { + String key = getEntryMapKey(record); + WarningEntry entry = entryMap.get(key); + if (entry != null) { + createShape(target, entry); + } + } + + protected void insertShape(IGraphicsTarget target, AbstractWarningRecord record) throws VizException { + String key = getEntryMapKey(record); + WarningEntry entry = entryMap.get(key); + if (entry == null) { + entry = new WarningEntry(); + entryMap.put(key, entry); + } + entry.record = record; // ...possibly replacing an existing record + if (! record.getUgcZones().isEmpty()) { + setGeometry(record); + } else { + entry.record.setGeometry(null); + } + createShape(target, entry); + } + + protected void createShape(IGraphicsTarget target, WarningEntry entry) throws VizException { + if (entry.shadedShape != null) { + entry.shadedShape.dispose(); + entry.shadedShape = null; + } + AbstractWarningRecord record = entry.record; + if (record.getGeometry() != null) { + IShadedShape ss = target.createShadedShape(false, + descriptor.getGridGeometry(), false); + Geometry geo = (Geometry) record.getGeometry().clone(); + JTSCompiler jtsCompiler = new JTSCompiler(ss, null, + this.descriptor, PointStyle.CROSS); + jtsCompiler.handle(geo, color); + ss.setFillPattern(FillPatterns.getGLPattern(record.getPhen() + .equals("TO") ? "VERTICAL" : "HORIZONTAL")); + ss.compile(); + entry.shadedShape = ss; + } + } + + /** + * Groups all the ugc zones with the same action, phensig, ETN, site, and + * issuance time. + */ + protected List mergeWatches( + List watchrecs) { + SimpleDateFormat sdfi = sdf.get(); + + Map watches = new HashMap(); + for (AbstractWarningRecord watchrec : watchrecs) { + if (watchrec.getIssueTime() == null) + continue; + + String key = watchrec.getAct() + '.' + watchrec.getPhensig() + '.' + + watchrec.getEtn() + '.' + watchrec.getOfficeid() + '.' + + sdfi.format(watchrec.getIssueTime().getTime()); + AbstractWarningRecord watch = watches.get(key); + if (watch == null) { + watch = watchrec; + watches.put(key, watch); + } else { + Set ugcZones = watch.getUgcZones(); + if (ugcZones != null) { + ugcZones.addAll(watchrec.getUgcZones()); + } + } + } + + ArrayList mergedWatches = new ArrayList( + watches.values()); + Collections.sort(mergedWatches, comparator); + + return mergedWatches; + } + + protected String getEntryMapKey(AbstractWarningRecord rec) { + return sdf.get().format(rec.getIssueTime().getTime()) + '.' + + rec.getWmoid() + '.' + rec.getPhensig() + '.' + rec.getEtn(); + } + + @Override + protected String getEventKey(WarningEntry entry) { + AbstractWarningRecord r = entry.record; + return r.getPhensig() + '.' + r.getEtn(); + } + + private void noteCwaUgcs(AbstractWarningRecord watchRec) { + String siteKey = watchRec.getXxxid(); + Set recUgcs = watchRec.getUgcZones(); + if (siteKey == null || recUgcs == null) + return; + + synchronized (cwaUgcMap) { + Set ugcs = cwaUgcMap.get(siteKey); + if (ugcs == null) { + ugcs = new HashSet(); + cwaUgcMap.put(siteKey, ugcs); + } + ugcs.addAll(recUgcs); + } + } + + @Override + protected void disposeInternal() { + synchronized(this) { + if (timerTask != null) + timerTask.cancel(); + } + super.disposeInternal(); + } + + private void scheduleNextTime() { + /* TODO: This is a race condition. Need the last frame time, + * but getLastFrameTimeRange() is also a race condition. So really need + * last drawn last frame time, but LAST_FRAME_ADJ is an hour ahead??! + */ + long vnow = SimulatedTime.getSystemTime().getMillis(); + AbstractWarningRecord best = null; + for (WarningEntry entry : entryMap.values()) { + AbstractWarningRecord rec = entry.record; + if (rec.getEndTime().getTimeInMillis() >= vnow && + (best == null || rec.getEndTime().before(best.getEndTime()))) { + best = rec; + } + } + if (best != null) { + scheduleTimer(best.getEndTime().getTimeInMillis() - vnow); + } else { + scheduleTimer(-1); + } + } + + private synchronized void scheduleTimer(long delay) { + if (timerTask != null) { + timerTask.cancel(); + } + if (delay >= 0) { + timerTask = new WatchesTimerTask(); + getTimer().schedule(timerTask, delay); + } else { + timerTask = null; + } + } + + private Timer getTimer() { + if (timer == null) { + synchronized (WouWcnWatchesResource.class) { + if (timer == null) { + timer = new Timer(WouWcnWatchesResource.class.getName() + " Timer"); + } + } + } + return timer; + } + + @Override + public void timechanged() { + issueRefresh(); + scheduleNextTime(); + } + + protected class WatchesTimerTask extends TimerTask { + + @Override + public void run() { + timechanged(); + } + } + + protected static class WouWcnWatchesComparator implements Comparator { + + static final WouWcnWatchesComparator instance = new WouWcnWatchesComparator(); + + public static Comparator getInstance() { + return instance; + } + + @Override + public int compare(AbstractWarningRecord a, AbstractWarningRecord b) { + int r; + Calendar ca = a.getIssueTime(); + Calendar cb = b.getIssueTime(); + if (ca == null) { + if (cb == null) + r = 0; + else + return 1; + } else if (cb == null) + return -1; + else + r = ca.compareTo(cb); + if (r != 0) + return r; + + // The point of this is to handle the BBB field, but it makes the TTAAii part significant too... + String sa = safeWmo(a); + String sb = safeWmo(b); + r = sa.compareTo(sb); + if (r != 0) + return r; + + r = a.getSeg() - b.getSeg(); + if (r != 0) + return r; + + return 0; + } + } + + private static String safeWmo(AbstractWarningRecord record) { + String wmo = record.getWmoid(); + return wmo != null ? wmo : "TTAA00 CCCC 000000"; + } + +} diff --git a/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResourceData.java b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResourceData.java new file mode 100644 index 0000000000..b90996953a --- /dev/null +++ b/cave/com.raytheon.viz.warnings/src/com/raytheon/viz/warnings/rsc/WouWcnWatchesResourceData.java @@ -0,0 +1,56 @@ +package com.raytheon.viz.warnings.rsc; + +import java.util.ArrayList; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import com.raytheon.uf.common.dataplugin.PluginDataObject; +import com.raytheon.uf.common.dataplugin.warning.AbstractWarningRecord; +import com.raytheon.uf.common.dataplugin.warning.WarningRecord.WarningAction; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.rsc.AbstractVizResource; +import com.raytheon.uf.viz.core.rsc.LoadProperties; + +/** + * Displays WOUs updated by WCNs + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * 2014-08-28   ASM #15682 D. Friemdan Initial creation
+ * 
+ * + */ +@XmlAccessorType(XmlAccessType.NONE) +public class WouWcnWatchesResourceData extends WWAResourceData { + + @Override + protected AbstractVizResource constructResource( + LoadProperties loadProperties, PluginDataObject[] objects) + throws VizException { + // add records + records = new ArrayList(objects.length); + for (int i = 0; i < objects.length; i++) { + AbstractWarningRecord r = (AbstractWarningRecord) objects[i]; + records.add(r); + } + + return new WouWcnWatchesResource(this, loadProperties); + } + + @Override + protected boolean isRecordTimeImportant(AbstractWarningRecord warnRec) { + WarningAction act = WarningAction.valueOf(warnRec.getAct()); + if (("WOU".equals(warnRec.getPil()) && WarningAction.CON == act) || + ("WCN".equals(warnRec.getPil()) && WarningAction.NEW == act)) { + return false; + } else { + return super.isRecordTimeImportant(warnRec); + } + } +} +