diff --git a/cave/com.raytheon.viz.pointdata/META-INF/MANIFEST.MF b/cave/com.raytheon.viz.pointdata/META-INF/MANIFEST.MF index ee45fd9b46..48a909f512 100644 --- a/cave/com.raytheon.viz.pointdata/META-INF/MANIFEST.MF +++ b/cave/com.raytheon.viz.pointdata/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Pointdata Plug-in Bundle-SymbolicName: com.raytheon.viz.pointdata;singleton:=true -Bundle-Version: 1.15.0.qualifier +Bundle-Version: 1.15.1.qualifier Bundle-Vendor: Raytheon Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 diff --git a/cave/com.raytheon.viz.pointdata/localization/bundles/Scatterometer.xml b/cave/com.raytheon.viz.pointdata/localization/bundles/Scatterometer.xml deleted file mode 100644 index d31a24daf6..0000000000 --- a/cave/com.raytheon.viz.pointdata/localization/bundles/Scatterometer.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/MetarPrecipResource.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/MetarPrecipResource.java index f8e1d6aae3..14ebbabdc4 100644 --- a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/MetarPrecipResource.java +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/MetarPrecipResource.java @@ -21,6 +21,7 @@ package com.raytheon.viz.pointdata.rsc; import java.awt.Font; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -75,6 +76,8 @@ import com.raytheon.uf.viz.core.rsc.LoadProperties; import com.raytheon.uf.viz.core.rsc.capabilities.ColorableCapability; import com.raytheon.uf.viz.core.rsc.capabilities.DensityCapability; import com.raytheon.uf.viz.core.rsc.capabilities.MagnificationCapability; +import com.raytheon.viz.pointdata.rsc.progdisc.GenericProgressiveDisclosure; +import com.raytheon.viz.pointdata.rsc.progdisc.GenericProgressiveDisclosure.PlotItem; import com.raytheon.viz.pointdata.util.MetarPrecipDataContainer; import com.raytheon.viz.pointdata.util.MetarPrecipDataContainer.PrecipData; import com.vividsolutions.jts.geom.Coordinate; @@ -90,13 +93,14 @@ import com.vividsolutions.jts.geom.Coordinate; * * SOFTWARE HISTORY * - * Date Ticket# Engineer Description - * ------------ ---------- ----------- -------------------------- - * Aug 19, 2011 bsteffen Initial creation - * Jun 07, 2013 2070 bsteffen Add geospatial constraints to metar - * precip requests. - * Mar 11, 2014 #2718 randerso Changes for GeoTools 10.5 + * Date Ticket# Engineer Description + * ------------- -------- --------- -------------------------------------------- + * Aug 19, 2011 7725 bsteffen Initial creation + * Jun 07, 2013 2070 bsteffen Add geospatial constraints to metar precip + * requests. + * Mar 11, 2014 2718 randerso Changes for GeoTools 10.5 * Nov 05, 2015 5070 randerso Adjust font sizes for dpi scaling + * Nov 13, 2015 4903 bsteffen Extract progressive disclosure * * * @@ -110,15 +114,19 @@ public class MetarPrecipResource extends private static final int PLOT_PIXEL_SIZE = 30; - private class RenderablePrecipData extends PrecipData { + private class RenderablePrecipData extends PrecipData implements PlotItem { - Double distValue = 0.0; + public final DrawableString string; - DrawableString string = null; - - public RenderablePrecipData(PrecipData data) { + public RenderablePrecipData(PrecipData data, DrawableString string) { super(data.getTimeObs(), data.getStationName(), data.getPrecipAmt(), data.getLatLon().x, data.getLatLon().y); + this.string = string; + } + + @Override + public Coordinate getLocation() { + return new Coordinate(string.basics.x, string.basics.y); } } @@ -150,7 +158,7 @@ public class MetarPrecipResource extends private boolean reproject = false; - private Map> data = new HashMap>(); + private Map> data = new HashMap<>(); private IFont font = null; @@ -176,19 +184,27 @@ public class MetarPrecipResource extends if (time == null) { return; } - List precips = getPrecipData(time); - if (precips == null) { + GenericProgressiveDisclosure disclosure = null; + synchronized (data) { + disclosure = data.get(time); + } + if (disclosure == null) { dataProcessJob.schedule(); return; } - if (precips.isEmpty()) { - return; - } - - RGB color = getCapability(ColorableCapability.class).getColor(); + IExtent extent = paintProps.getView().getExtent(); Double magnification = getCapability(MagnificationCapability.class) .getMagnification(); Double density = getCapability(DensityCapability.class).getDensity(); + double threshold = (PLOT_PIXEL_SIZE * magnification) / density; + threshold = (threshold * extent.getWidth()) + / paintProps.getCanvasBounds().width; + Collection precips = disclosure.runDisclosure( + extent, threshold); + if (precips.isEmpty()) { + return; + } + RGB color = getCapability(ColorableCapability.class).getColor(); if (font == null) { font = target.initializeFont(Font.DIALOG, 8, @@ -198,37 +214,23 @@ public class MetarPrecipResource extends List strings = new ArrayList(); - IExtent extent = paintProps.getView().getExtent(); - - double threshold = (PLOT_PIXEL_SIZE * magnification) / density; - threshold = (threshold * extent.getWidth()) - / paintProps.getCanvasBounds().width; - for (RenderablePrecipData data : precips) { - if (!extent.contains(new double[] { data.string.basics.x, - data.string.basics.y })) { - continue; - } - if (data.distValue >= threshold) { - // This is easier then changing it when the capability changes. - data.string.font = this.font; - data.string.setText(data.string.getText(), color); - strings.add(data.string); - } + // This is easier then changing it when the capability changes. + data.string.font = this.font; + data.string.setText(data.string.getText(), color); + strings.add(data.string); } target.drawStrings(strings); } private List getPrecipData(DataTime time) { - List currData = null; + GenericProgressiveDisclosure currData = null; synchronized (data) { currData = data.get(time); } if (currData != null) { - synchronized (currData) { - return new ArrayList(currData); - } + return currData.getAll(); } return null; } @@ -336,7 +338,9 @@ public class MetarPrecipResource extends GridEnvelope2D envelope = GridGeometry2D.wrap( descriptor.getGridGeometry()).getGridRange2D(); synchronized (data) { - for (List dataList : data.values()) { + for (GenericProgressiveDisclosure disclosure : data + .values()) { + List dataList = disclosure.getAll(); Iterator it = dataList.iterator(); while (it.hasNext()) { RenderablePrecipData precip = it.next(); @@ -425,22 +429,20 @@ public class MetarPrecipResource extends rcMap.put("location.stationId", rc); MetarPrecipDataContainer container = new MetarPrecipDataContainer( resourceData.getDuration(), rcMap); - for (Entry> entry : data + for (Entry> entry : data .entrySet()) { DataTime time = entry.getKey(); if (time.getMatchValid() < earliestTime) { // No need to reprocess times after the earliest update. continue; } - synchronized (entry.getValue()) { - Iterator iter = entry.getValue() - .iterator(); - while (iter.hasNext()) { - if (newStations.contains(iter.next().getStationName())) { - iter.remove(); - } + GenericProgressiveDisclosure newValue = new GenericProgressiveDisclosure<>(); + for (RenderablePrecipData data : entry.getValue().getAll()) { + if (!newStations.contains(data.getStationName())) { + newValue.add(data); } } + entry.setValue(newValue); addData(time, container.getBasePrecipData(time)); addData(time, container.getDerivedPrecipData(time)); if (monitor.isCanceled()) { @@ -523,10 +525,9 @@ public class MetarPrecipResource extends private void addData(DataTime time, List precips) { if (precips.isEmpty()) { if (!dataTimes.contains(time)) { + GenericProgressiveDisclosure disclosure = new GenericProgressiveDisclosure(); synchronized (data) { - List newPrecips = Collections - .emptyList(); - data.put(time, newPrecips); + data.put(time, disclosure); } dataTimes.add(time); } @@ -544,8 +545,7 @@ public class MetarPrecipResource extends }); - List newPrecips = new ArrayList( - precips.size()); + GenericProgressiveDisclosure newPrecips = new GenericProgressiveDisclosure(); RGB color = getCapability(ColorableCapability.class).getColor(); @@ -558,32 +558,19 @@ public class MetarPrecipResource extends if (precip instanceof RenderablePrecipData) { data = (RenderablePrecipData) precip; } else { - data = new RenderablePrecipData(precip); double[] px = descriptor.worldToPixel(new double[] { precip.getLatLon().x, precip.getLatLon().y }); if (!envelope.contains(px[0], px[1])) { continue; } - data.string = new DrawableString(formatPrecip(precips.get(i) - .getPrecipAmt()), color); - data.string.setCoordinates(px[0], px[1], px[2]); - data.string.verticallAlignment = VerticalAlignment.MIDDLE; - data.string.horizontalAlignment = HorizontalAlignment.CENTER; - } - double bestDist = Double.MAX_VALUE; - for (RenderablePrecipData exist : newPrecips) { - double xDist = exist.string.basics.x - data.string.basics.x; - double yDist = exist.string.basics.y - data.string.basics.y; - double dist = Math.hypot(xDist, yDist); - if (dist < bestDist) { - bestDist = dist; - } - } - data.distValue = bestDist; - // this checks removes duplicates - if (bestDist > 0) { - newPrecips.add(data); + DrawableString string = new DrawableString(formatPrecip(precips + .get(i).getPrecipAmt()), color); + string.setCoordinates(px[0], px[1], px[2]); + string.verticallAlignment = VerticalAlignment.MIDDLE; + string.horizontalAlignment = HorizontalAlignment.CENTER; + data = new RenderablePrecipData(precip, string); } + newPrecips.add(data); } synchronized (data) { data.put(time, newPrecips); diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/progdisc/GenericProgressiveDisclosure.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/progdisc/GenericProgressiveDisclosure.java new file mode 100644 index 0000000000..bbb8cbd05d --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/progdisc/GenericProgressiveDisclosure.java @@ -0,0 +1,361 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.progdisc; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import com.raytheon.uf.viz.core.IExtent; +import com.raytheon.viz.pointdata.rsc.progdisc.GenericProgressiveDisclosure.PlotItem; +import com.vividsolutions.jts.geom.Coordinate; + +/** + * A generic progressive disclosure algorithm that will determine a subset of + * {@link PlotItem}s that should be displayed given a particular {@link IExtent} + * and the desired distance between plot items. The exact disclosure for a set + * of items can be weighted by adding the items in a specific order. In general + * the items that are added first will displayed sooner than those added later. + * For example if you were displaying cities, then sorting the cities by + * population before adding would result in large cities being displayed before + * small cities. + *

+ * This class was designed to scale well for any number of points. To achieve + * this it uses a combination of two distinct algorithms with different + * performance characteristics: + *

    + *
  1. The static algorithm: When a new item is inserted using this + * algorithm then the position is compared to all the existing items to + * determine the distance from the closest item. When disclosure is run it is + * simply a loop over the items to find any with a large enough distance value. + * This has Θ(n²) insertion time, Θ(n) disclosure time. + *
  2. The dynamic algorithm: When a new item is inserted using this + * algorithm it is simply appended to the list of items. When disclosure is run + * then each item is compared to any other displayed items to see if it fits + * into the display without getting to close. This has Θ(1) insertion time, + * Θ(n*m) disclosure time(m is the number of items actually displayed). + *

    + * The static algorithm is much better for performance if the disclosure is + * going to be reused since it has a much faster disclosure time. However the + * longer insertion time can be a problem for more than 5000-10000 items. + *

    + * Besides raw performance, a major consideration between the two algorithms is + * that the dynamic algorithm suffers from inconsistent disclosure that can + * cause a flickering artifact as the extent changes. For example if you zoom in + * on a display then sometimes there is enough room to replace a single item + * with two items that are spread further apart, however if those items are too + * close to other items then they may be removed and replaced with items + * slightly further away. A very small change to the extent leads to a very big + * change in the set of items displayed which is generally unpleasant for users. + *

    + * There are two tweaks to the dynamic algorithm that help mitigate the flicker. + *

      + *
    1. First, when the dynamic algorithm is used it caches the disclosed items + * and tries to disclose the same items across multiple calls, before + * considering new items. This helps immensely because as the extent changes + * only minimal changes are made to the set of disclosed items. There can still + * be a problem if you zoom in on an area and zoom back out, the extra items + * introduced by zooming in can significantly change the result when zooming + * out. Although this effect is much more gradual than a flicker, it is still + * annoying when there is a specific item of interest that disappears + * unexpectedly. + *
    2. Secondly, pure dynamic disclosure is never used, instead a hybrid of + * dynamic/static is always used. For the first few thousand items the insertion + * cost for the static algorithm is very low so using a static algorithm for a + * certain number of points is beneficial. The static items can be quickly + * disclosed before adding in the dynamic points and they act like anchor points + * that help keep the dynamic algorithm more consistent. + *

      + * The hybrid approach provides a balance between getting something to display + * quickly and keeping the disclosure consistent. If the disclosure is going to + * be used for awhile it may be worthwhile to use the static disclosure more. It + * is possible to asynchronously calculate the static values so that as soon as + * possible the disclosure will be completely consistent( + * {@link #calculateStaticDistances()}. + *

      + * This class uses internal synchronization to make it safe to use from multiple + * threads. When properly configured all function calls will be very fast so + * there should not be significant waiting. + * + *

      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 12, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + * @param + */ +public class GenericProgressiveDisclosure { + + private final int staticToDynamicThreshold; + + private AtomicInteger waitingTasks = new AtomicInteger(0); + + private int staticCount = 0; + + private List> items = new ArrayList<>(); + + private List> prevItems = Collections.emptyList(); + + public GenericProgressiveDisclosure() { + this(5000); + } + + /** + * Construct a new instance with a specific threshold for switching between + * the two disclosure algorithms. Its usually best to keep the number + * relatively small and use {@link #calculateStaticDistances()} + * asynchronously to ensure that that + * {@link #runDisclosure(IExtent, double)} does not have to wait for slow + * calls to {@link #add(PlotItem)}. + * + * @param staticToDynamicThreshold + * the number of items that should calculate static distance + * values before switching to dynamic calculations. + */ + public GenericProgressiveDisclosure(int staticToDynamicThreshold) { + this.staticToDynamicThreshold = staticToDynamicThreshold; + } + + /** + * @return all the {@link PlotItem}s contained in this disclosure. + */ + public List getAll() { + ArrayList all = new ArrayList<>(); + waitingTasks.incrementAndGet(); + synchronized (items) { + all.ensureCapacity(items.size()); + for (ItemInfo info : items) { + all.add(info.getItem()); + } + } + waitingTasks.decrementAndGet(); + return all; + } + + /** + * Get a set of items that are all within extent and are not closer together + * than minDistance. + * + * @param extent + * area whcich will contain all items returned + * @param minDistance + * the minimum distance between any two disclosed items, no two + * items returned will be closer than this. + * @return items + */ + public Collection runDisclosure(IExtent extent, double minDistance) { + List> items = new ArrayList<>(); + Set result = new HashSet<>(100); + waitingTasks.incrementAndGet(); + synchronized (this.items) { + for (ItemInfo item : getStaticItems()) { + if (item.getDistance() > minDistance && item.within(extent)) { + result.add(item.getItem()); + items.add(item); + } + } + if (staticCount < this.items.size()) { + for (ItemInfo item : this.prevItems) { + if (!item.hasDistance() && item.within(extent) + && getMinDistance(items, item) > minDistance) { + if (result.add(item.getItem())) { + items.add(item); + } + } + } + for (ItemInfo item : getDynamicItems()) { + if (item.within(extent) + && getMinDistance(items, item) > minDistance) { + if (result.add(item.getItem())) { + items.add(item); + } + } + } + this.prevItems = items; + } + } + waitingTasks.decrementAndGet(); + return result; + } + + /** + * Add a new item that can be disclosed. This will not do any aggressive + * duplicate checking. + */ + public double add(T item) { + return add(item, false); + } + + /** + * Add a new item that can be disclosed. + * + * @param item + * the item to add + * @param checkDuplicate + * when this is true then the item will be checked against all + * existing items and ignored if it is in a location that is + * already used. This slows down the method so if you are + * reasonably certain there are no duplicates its better to use + * false. + * @return The static distance between the new item and the next closest + * item, or NaN if there are too many items for static calculations. + */ + public double add(T item, boolean checkDuplicate) { + ItemInfo info = new ItemInfo<>(item); + waitingTasks.incrementAndGet(); + synchronized (items) { + if (items.size() < staticToDynamicThreshold) { + double distance = getMinDistance(items, info); + info.setDistance(distance); + staticCount += 1; + } else if (checkDuplicate) { + for (ItemInfo itemToCheck : items) { + if (itemToCheck.getItem().getLocation() + .equals(item.getLocation())) { + info.setDistance(0); + break; + } + } + } + if (!info.hasDistance() || info.getDistance() > 0) { + items.add(info); + + } + } + waitingTasks.decrementAndGet(); + return info.getDistance(); + } + + /** + * Convert any items within the disclosure that are using the dynamic + * algorithm to be able to use the static algorithm. This will result in + * more consistent disclosure and faster execution of + * {@link #runDisclosure(IExtent, double)}. If there are alot of items this + * method can be slow and should not be run on a thread that is sensitive to + * delays. + *

      + * Running the calculation requires an exclusive lock that can block other + * methods. to avoid causing problems this method will return early if there + * is another thread using this object. The early return will often occur + * after some items have been converted so future calls will have less work + * to do. If the return value indicates an early return then the calling + * code should generally {@link Thread#sleep(long)}, {@link Thread#yield()}, + * or do other tasks before calling this method again. Calling this method + * again before other threads have a chance to run will return immediately + * after doing no work. + * + * @return false if the calculation was interrupted and there are more + * dynamic items, true if this disclosure is now 100% static. + */ + public boolean calculateStaticDistances() { + if (waitingTasks.get() > 0) { + return false; + } + synchronized (items) { + while (staticCount < items.size()) { + ItemInfo item = items.get(staticCount); + double distance = getMinDistance(getStaticItems(), item); + if (distance > 0) { + item.setDistance(distance); + staticCount += 1; + } else { + items.remove(staticCount); + } + if (waitingTasks.get() > 0) { + return false; + } + } + return true; + } + } + + protected List> getStaticItems() { + return items.subList(0, staticCount); + } + + protected List> getDynamicItems() { + return items.subList(staticCount, items.size()); + } + + private double getMinDistance(List> list, ItemInfo item) { + double min_dist = Double.MAX_VALUE; + for (ItemInfo existing : list) { + double dist = existing.getItem().getLocation() + .distance(item.getItem().getLocation()); + if (dist < min_dist + && (Double.isNaN(existing.getDistance()) || dist < existing + .getDistance())) { + min_dist = dist; + } + } + return min_dist; + + } + + public static interface PlotItem { + + public Coordinate getLocation(); + } + + private static class ItemInfo { + + private final T item; + + private double distance; + + public ItemInfo(T item) { + this.item = item; + this.distance = Double.NaN; + } + + public T getItem() { + return item; + } + + public boolean within(IExtent extent) { + Coordinate location = item.getLocation(); + return extent.contains(new double[] { location.x, location.y }); + } + + public boolean hasDistance() { + return !Double.isNaN(distance); + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + } +} diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotConfig.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotConfig.java new file mode 100644 index 0000000000..ee9174b1da --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotConfig.java @@ -0,0 +1,152 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.wind; + +import java.util.HashSet; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +/** + * + * JAXB compatible representation of all the configuration options needed to + * request data and sampling information for wind vectors. + * + *

      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 13, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + */ +@XmlAccessorType(XmlAccessType.NONE) +public class WindPlotConfig { + + @XmlElement + private WindPlotParameter longitude = new WindPlotParameter("longitude"); + + @XmlElement + private WindPlotParameter latitude = new WindPlotParameter("latitude"); + + @XmlElement + private WindPlotParameter speed = new WindPlotParameter("windSpd", "kn"); + + @XmlElement + private WindPlotParameter direction = new WindPlotParameter("windDir"); + + @XmlElement + private SampleFormat sample; + + public WindPlotParameter getLongitude() { + return longitude; + } + + public void setLongitude(WindPlotParameter longitude) { + this.longitude = longitude; + } + + public WindPlotParameter getLatitude() { + return latitude; + } + + public void setLatitude(WindPlotParameter latitude) { + this.latitude = latitude; + } + + public WindPlotParameter getSpeed() { + return speed; + } + + public void setSpeed(WindPlotParameter speed) { + this.speed = speed; + } + + public WindPlotParameter getDirection() { + return direction; + } + + public void setDirection(WindPlotParameter direction) { + this.direction = direction; + } + + public SampleFormat getSample() { + return sample; + } + + public void setSample(SampleFormat sample) { + this.sample = sample; + } + + public Set getUniqueParameters() { + Set result = new HashSet<>(5); + result.add(longitude.getParameter()); + result.add(latitude.getParameter()); + result.add(speed.getParameter()); + result.add(direction.getParameter()); + if (sample != null) { + result.addAll(sample.getUniqueParameters()); + } + return result; + } + + @XmlAccessorType(XmlAccessType.NONE) + public static class SampleFormat { + + @XmlAttribute + private String text; + + @XmlElement(name = "field") + private WindPlotParameter[] fields; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public WindPlotParameter[] getFields() { + return fields; + } + + public void setFields(WindPlotParameter[] fields) { + this.fields = fields; + } + + public Set getUniqueParameters() { + Set result = new HashSet<>(); + for (WindPlotParameter p : fields) { + result.add(p.getParameter()); + } + return result; + } + + } +} diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotParameter.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotParameter.java new file mode 100644 index 0000000000..273eb2d726 --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotParameter.java @@ -0,0 +1,236 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.wind; + +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Date; + +import javax.measure.unit.Unit; +import javax.measure.unit.UnitFormat; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; + +import com.raytheon.uf.common.pointdata.PointDataDescription.Type; +import com.raytheon.uf.common.pointdata.PointDataView; +import com.raytheon.uf.common.status.IUFStatusHandler; +import com.raytheon.uf.common.status.UFStatus; +import com.raytheon.uf.common.status.UFStatus.Priority; +import com.raytheon.uf.common.time.util.TimeUtil; + +/** + * + * Configuration information for a single parameter that is part of a + * {@link WindPlotConfig}. + * + *
      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 13, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + */ +@XmlAccessorType(XmlAccessType.NONE) +public class WindPlotParameter { + + private static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(WindPlotParameter.class); + + @XmlAttribute + private String parameter; + + @XmlAttribute + private String unit; + + @XmlAttribute + private Boolean time; + + @XmlElement(name = "format") + private FormatValue[] formats; + + @XmlTransient + private Unit parsedUnit; + + /* Avoid spamming logs. */ + @XmlTransient + private boolean unitParseFailed = false; + + public WindPlotParameter() { + + } + + public WindPlotParameter(String parameter) { + this.parameter = parameter; + } + + public WindPlotParameter(String parameter, String unit) { + this.parameter = parameter; + this.unit = unit; + } + + public String getParameter() { + return parameter; + } + + public void setParameter(String parameter) { + this.parameter = parameter; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public Boolean getTime() { + return time; + } + + public void setTime(Boolean time) { + this.time = time; + } + + public FormatValue[] getFormats() { + return formats; + } + + public void setFormats(FormatValue[] formats) { + this.formats = formats; + } + + public Unit getParsedUnit() { + if (!unitParseFailed && parsedUnit == null) { + try { + parsedUnit = UnitFormat.getUCUMInstance().parseProductUnit( + unit, new ParsePosition(0)); + } catch (ParseException e) { + statusHandler.handle(Priority.WARN, + "Error retrieving wind data.", e); + unitParseFailed = true; + } + } + return parsedUnit; + } + + public double getNumericValue(PointDataView view) { + double value = view.getNumber(parameter).doubleValue(); + if (unit != null) { + Unit desired = getParsedUnit(); + Unit current = view.getUnit(parameter); + if (desired != null && current.isCompatible(desired)) { + value = current.getConverterTo(desired).convert(value); + } + } + return value; + } + + public Object getValue(PointDataView view) { + if (view.getType(parameter) == Type.STRING) { + return view.getString(parameter); + } else if (Boolean.TRUE.equals(time)) { + return TimeUtil.newGmtCalendar(new Date(view.getNumber(parameter) + .longValue())); + } else if (formats != null) { + Number value = view.getNumber(parameter); + for (FormatValue format : formats) { + if (format.matches(value)) { + return format.getText(); + } + } + return value.toString(); + } else if (unit != null) { + return getNumericValue(view); + } + return view.getNumber(parameter); + } + + @XmlAccessorType(XmlAccessType.NONE) + public static class FormatValue { + + @XmlAttribute + private Double high; + + @XmlAttribute + private Double low; + + @XmlAttribute + private Double value; + + @XmlAttribute + private String text; + + public boolean matches(Number value) { + if (this.value != null + && value.doubleValue() == this.value.doubleValue()) { + return true; + } else if (high != null && low != null + && high.doubleValue() >= value.doubleValue() + && low.doubleValue() < value.doubleValue()) { + return true; + } + return false; + } + + public Double getHigh() { + return high; + } + + public void setHigh(Double high) { + this.high = high; + } + + public Double getLow() { + return low; + } + + public void setLow(Double low) { + this.low = low; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + } + +} \ No newline at end of file diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotRenderable.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotRenderable.java new file mode 100644 index 0000000000..c797b42865 --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotRenderable.java @@ -0,0 +1,268 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.wind; + +import java.util.Collection; + +import org.eclipse.swt.graphics.RGB; +import org.geotools.referencing.GeodeticCalculator; + +import com.raytheon.uf.viz.core.IExtent; +import com.raytheon.uf.viz.core.IGraphicsTarget; +import com.raytheon.uf.viz.core.drawables.IDescriptor; +import com.raytheon.uf.viz.core.drawables.PaintProperties; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.point.display.VectorGraphicsConfig; +import com.raytheon.uf.viz.core.point.display.VectorGraphicsRenderable; +import com.raytheon.uf.viz.core.rsc.AbstractVizResource; +import com.raytheon.uf.viz.core.rsc.IResourceDataChanged; +import com.raytheon.uf.viz.core.rsc.capabilities.ColorableCapability; +import com.raytheon.uf.viz.core.rsc.capabilities.DensityCapability; +import com.raytheon.uf.viz.core.rsc.capabilities.MagnificationCapability; +import com.raytheon.viz.pointdata.rsc.progdisc.GenericProgressiveDisclosure; +import com.raytheon.viz.pointdata.rsc.progdisc.GenericProgressiveDisclosure.PlotItem; +import com.vividsolutions.jts.geom.Coordinate; + +/** + * + * Combines a {@link VectorGraphicsRenderable} with a + * {@link GenericProgressiveDisclosure} to generate progressively disclosed wind + * displays. + * + *
      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 13, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + */ +public class WindPlotRenderable { + + protected final AbstractVizResource resource; + + private final IResourceDataChanged changeListener = new IResourceDataChanged() { + + @Override + public void resourceChanged(ChangeType type, Object object) { + if (type == ChangeType.CAPABILITY) { + lastPaintedExtent = null; + } + } + }; + + protected VectorGraphicsConfig config = new VectorGraphicsConfig(); + + protected double baseDensity = 1.0; + + protected GenericProgressiveDisclosure barbs = new GenericProgressiveDisclosure<>(); + + private VectorGraphicsRenderable renderable; + + private double lastPaintedDistance = Double.POSITIVE_INFINITY; + + private IExtent lastPaintedExtent; + + public WindPlotRenderable(AbstractVizResource resource) { + this.resource = resource; + resource.getResourceData().addChangeListener(changeListener); + } + + public VectorGraphicsConfig getConfig() { + return config; + } + + public void reconfigure() { + if (lastPaintedExtent != null) { + lastPaintedExtent = null; + resource.issueRefresh(); + } + } + + public void dispose() { + if (renderable != null) { + renderable.dispose(); + renderable = null; + } + resource.getResourceData().removeChangeListener(changeListener); + } + + public void paint(IGraphicsTarget target, PaintProperties paintProps) + throws VizException { + IExtent extent = paintProps.getView().getExtent(); + if (renderable != null + && (lastPaintedExtent == null || !lastPaintedExtent + .equals(extent))) { + renderable.dispose(); + renderable = null; + } + if (renderable == null) { + + double magnification = resource.getCapability( + MagnificationCapability.class).getMagnification(); + double ratio = magnification * extent.getWidth() + / paintProps.getCanvasBounds().width; + config.setSizeScaler(ratio); + renderable = new VectorGraphicsRenderable(getDescriptor(), target, + config); + lastPaintedExtent = extent.clone(); + double density = resource.getCapability(DensityCapability.class) + .getDensity(); + double minDist = config.getScaledSize() / baseDensity / density; + lastPaintedDistance = minDist; + Collection barbs = this.barbs.runDisclosure(extent, minDist); + GeodeticCalculator gc = new GeodeticCalculator(); + for (Barb barb : barbs) { + double dir = barb.getDirection(); + Coordinate lonLat = barb.getLonLat(); + Coordinate plotLoc = barb.getLocation(); + gc.setStartingGeographicPoint(lonLat.x, lonLat.y); + double[] world = getDescriptor().pixelToWorld( + new double[] { plotLoc.x, plotLoc.y - 1 }); + gc.setDestinationGeographicPoint(world[0], world[1]); + dir -= gc.getAzimuth(); + dir = Math.toRadians(dir); + renderable.paintBarb(plotLoc, barb.getMagnitude(), dir); + } + } + renderable.setColor(getColor()); + renderable.paint(target); + } + + public String getText(Coordinate pixelLoc) { + String text = null; + double bestDist = 100; + if (renderable != null) { + bestDist = renderable.getConfig().getScaledSize() * 2; + } + for (Barb barb : barbs.getAll()) { + double dist = barb.getLocation().distance(pixelLoc); + if (dist < bestDist) { + bestDist = dist; + text = barb.getText(); + } + } + return text; + } + + protected RGB getColor() { + return resource.getCapability(ColorableCapability.class).getColor(); + } + + public synchronized boolean optimizeDisclosure() { + return barbs.calculateStaticDistances(); + } + + public void addBarb(Coordinate lonLat, double magnitude, double direction) { + addBarb(lonLat, magnitude, direction, null, false); + } + + public void addBarb(Coordinate lonLat, double magnitude, double direction, + boolean checkDuplicate) { + addBarb(lonLat, magnitude, direction, null, checkDuplicate); + } + + public void addBarb(Coordinate lonLat, double magnitude, double direction, + String text) { + addBarb(lonLat, magnitude, direction, text, false); + } + + public synchronized void addBarb(Coordinate lonLat, double magnitude, + double direction, String text, boolean checkDuplicate) { + double[] pixel = getDescriptor().worldToPixel( + new double[] { lonLat.x, lonLat.y }); + Coordinate plotLoc = new Coordinate(pixel[0], pixel[1]); + double distance = barbs.add(new Barb(lonLat, plotLoc, magnitude, + direction, text), checkDuplicate); + if (Double.isNaN(distance) || distance > lastPaintedDistance) { + IExtent extent = lastPaintedExtent; + if (extent != null && extent.contains(pixel)) { + resource.issueRefresh(); + lastPaintedExtent = null; + } + } + } + + public synchronized void reproject() { + GenericProgressiveDisclosure barbs = this.barbs; + this.barbs = new GenericProgressiveDisclosure<>(); + for (Barb barb : barbs.getAll()) { + addBarb(barb.getLonLat(), barb.getMagnitude(), barb.getDirection()); + } + } + + protected IDescriptor getDescriptor() { + return resource.getDescriptor(); + } + + private class Barb implements PlotItem { + + public final Coordinate lonLat; + + public final Coordinate plotLoc; + + public final double magnitude; + + public final double direction; + + public final String text; + + public Barb(Coordinate lonLat, Coordinate plotLoc, double magnitude, + double direction, String text) { + this.lonLat = lonLat; + this.plotLoc = plotLoc; + this.magnitude = magnitude; + this.direction = direction; + this.text = text; + } + + public Coordinate getLonLat() { + return lonLat; + } + + @Override + public Coordinate getLocation() { + return plotLoc; + } + + public double getMagnitude() { + return magnitude; + } + + public double getDirection() { + return direction; + } + + public String getText() { + return text; + } + + } + + public void setBaseDensity(double baseDensity) { + this.baseDensity = baseDensity; + } + +} diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResource.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResource.java new file mode 100644 index 0000000000..4bada49793 --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResource.java @@ -0,0 +1,404 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.wind; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.TransformException; + +import com.raytheon.uf.common.dataplugin.PluginDataObject; +import com.raytheon.uf.common.dataquery.requests.RequestConstraint; +import com.raytheon.uf.common.geospatial.ReferencedCoordinate; +import com.raytheon.uf.common.pointdata.PointDataContainer; +import com.raytheon.uf.common.pointdata.PointDataServerRequest; +import com.raytheon.uf.common.pointdata.PointDataView; +import com.raytheon.uf.common.time.BinOffset; +import com.raytheon.uf.common.time.DataTime; +import com.raytheon.uf.common.time.TimeRange; +import com.raytheon.uf.viz.core.IGraphicsTarget; +import com.raytheon.uf.viz.core.drawables.PaintProperties; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.jobs.JobPool; +import com.raytheon.uf.viz.core.map.IMapDescriptor; +import com.raytheon.uf.viz.core.point.display.VectorGraphicsConfig; +import com.raytheon.uf.viz.core.requests.ThriftClient; +import com.raytheon.uf.viz.core.rsc.AbstractVizResource; +import com.raytheon.uf.viz.core.rsc.IResourceDataChanged.ChangeType; +import com.raytheon.uf.viz.core.rsc.LoadProperties; +import com.raytheon.viz.pointdata.rsc.wind.WindPlotConfig.SampleFormat; +import com.vividsolutions.jts.geom.Coordinate; + +/** + * + * Resource for displaying wind barbs of point data. + * + *
      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 13, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + */ +public class WindPlotResource extends + AbstractVizResource { + + private JobPool primaryLoadPool; + + private Job secondaryLoadJob; + + private final Object framesLock = new Object(); + + private Map frames; + + protected WindPlotResource(WindPlotResourceData resourceData, + LoadProperties loadProperties) { + super(resourceData, loadProperties); + } + + @Override + protected void initInternal(IGraphicsTarget target) throws VizException { + synchronized (framesLock) { + dataTimes = new CopyOnWriteArrayList<>(); + frames = new HashMap(); + primaryLoadPool = new JobPool("Loading Wind Data", 4, false); + secondaryLoadJob = new SecondaryUpdateJob(); + secondaryLoadJob.setSystem(true); + secondaryLoadJob.schedule(); + } + } + + @Override + protected void disposeInternal() { + secondaryLoadJob.cancel(); + secondaryLoadJob = null; + primaryLoadPool.cancel(); + primaryLoadPool = null; + synchronized (framesLock) { + for (DataFrame frame : frames.values()) { + frame.getRenderable().dispose(); + } + this.frames = null; + } + + } + + @Override + protected void paintInternal(IGraphicsTarget target, + PaintProperties paintProps) throws VizException { + DataTime time = paintProps.getDataTime(); + if (time == null) { + return; + } + DataFrame frame; + synchronized (framesLock) { + frame = frames.get(time); + if (frame == null) { + frame = addFrame(time); + } + } + primaryLoadPool.schedule(frame); + frame.getRenderable().paint(target, paintProps); + } + + protected DataFrame addFrame(DataTime dataTime) { + synchronized (framesLock) { + DataFrame frame = frames.get(dataTime); + if (frame == null) { + frame = new DataFrame(dataTime); + int index = Collections.binarySearch(dataTimes, dataTime); + if (index < 0) { + /* Keep the list sorted. */ + dataTimes.add(-1 * index - 1, dataTime); + } + frames.put(dataTime, frame); + } + return frame; + } + } + + @Override + public void remove(DataTime dataTime) { + synchronized (framesLock) { + frames.remove(dataTime); + super.remove(dataTime); + } + } + + @Override + public String inspect(ReferencedCoordinate coord) throws VizException { + if (resourceData.getConfig().getSample() == null) { + return null; + } + DataTime time = descriptor.getTimeForResource(this); + if (time == null) { + return null; + } + DataFrame frame; + synchronized (framesLock) { + frame = frames.get(time); + } + if (frame == null) { + return null; + } + try { + String text = frame.getRenderable().getText( + coord.asPixel(descriptor.getGridGeometry())); + if (text == null) { + text = "NO DATA"; + } + return text; + } catch (TransformException | FactoryException e) { + throw new VizException("Unable to transform point for inspect.", e); + } + } + + @Override + public void project(CoordinateReferenceSystem crs) throws VizException { + synchronized (framesLock) { + for (DataFrame frame : frames.values()) { + frame.reproject(); + } + } + } + + @Override + public String getName() { + String name = resourceData.getLegend(); + if (name == null) { + name = "Wind"; + } + return name; + } + + protected void loadData(DataTime dataTime, WindPlotRenderable renderable, + boolean initialLoad) { + WindPlotConfig config = resourceData.getConfig(); + SampleFormat sample = config.getSample(); + Map constraints = new HashMap<>( + resourceData.getFullDataMetadataMap()); + constraints.put(PointDataServerRequest.REQUEST_MODE_KEY, + new RequestConstraint(PointDataServerRequest.REQUEST_MODE_2D)); + constraints.put(PointDataServerRequest.REQUEST_PARAMETERS_KEY, + new RequestConstraint(config.getUniqueParameters())); + + BinOffset binOffset = resourceData.getBinOffset(); + if (binOffset == null) { + constraints.put(PluginDataObject.DATATIME_ID, + new RequestConstraint(dataTime.toString())); + } else { + TimeRange range = binOffset.getTimeRange(dataTime); + constraints.put(PluginDataObject.REFTIME_ID, new RequestConstraint( + new DataTime(range.getStart()).toString(), new DataTime( + range.getEnd()).toString())); + } + PointDataServerRequest request = new PointDataServerRequest(); + request.setRcMap(constraints); + try { + PointDataContainer container = (PointDataContainer) ThriftClient + .sendRequest(request); + if (container == null) { + return; + } + for (int i = 0; i < container.getAllocatedSz(); i += 1) { + PointDataView view = container.readRandom(i); + double longitude = config.getLongitude().getNumericValue(view); + double latitude = config.getLatitude().getNumericValue(view); + double magnitude = config.getSpeed().getNumericValue(view); + double direction = config.getDirection().getNumericValue(view); + Coordinate lonLat = new Coordinate(longitude, latitude); + if (sample == null) { + renderable.addBarb(lonLat, magnitude, direction, + !initialLoad); + } else { + WindPlotParameter[] fields = sample.getFields(); + Object[] args = new Object[fields.length]; + for (int c = 0; c < args.length; c += 1) { + args[c] = fields[c].getValue(view); + } + String text = String.format(sample.getText(), args) + .toString(); + renderable.addBarb(lonLat, magnitude, direction, text, + !initialLoad); + } + + } + } catch (VizException e) { + statusHandler.error("Error retrieving wind data.", e); + } + } + + @Override + protected void resourceDataChanged(ChangeType type, Object updateObject) { + if (type == ChangeType.DATA_UPDATE + && updateObject instanceof PluginDataObject[]) { + synchronized (framesLock) { + BinOffset binOffset = resourceData.getBinOffset(); + for (PluginDataObject pdo : (PluginDataObject[]) updateObject) { + DataTime time = pdo.getDataTime(); + if (binOffset != null) { + time = binOffset.getNormalizedTime(time); + } + DataFrame frame = frames.get(time); + if (frame != null) { + frame.update(); + } + } + secondaryLoadJob.schedule(); + } + } + } + + + private class DataFrame implements Runnable { + + private final DataTime time; + + private final WindPlotRenderable renderable; + + private boolean needsReproject = false; + + private boolean needsData = true; + + private boolean initialLoad = true; + + public DataFrame(DataTime time) { + this.time = time; + this.renderable = new WindPlotRenderable(WindPlotResource.this); + renderable.setBaseDensity(resourceData.getBaseDensity()); + VectorGraphicsConfig config = renderable.getConfig(); + config.setBaseSize(20); + config.setCalmCircleSizeRatio(0.3); + } + + public WindPlotRenderable getRenderable() { + return renderable; + } + + public void update() { + needsData = true; + } + + public void reproject() { + needsReproject = true; + } + + @Override + public void run() { + asyncUpdate(); + } + + public synchronized boolean asyncUpdate() { + boolean result = needsReproject | needsData; + if (needsReproject) { + needsReproject = false; + renderable.reproject(); + } + if (needsData) { + needsData = false; + loadData(time, renderable, initialLoad); + initialLoad = false; + } + return result; + } + + } + + private class SecondaryUpdateJob extends Job { + + public SecondaryUpdateJob() { + super("Updating wind data."); + setSystem(true); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + List frameList; + synchronized (framesLock) { + if (frames == null) { + return Status.OK_STATUS; + } + frameList = new ArrayList<>(frames.values()); + } + for (DataFrame frame : frameList) { + if (frame.asyncUpdate()) { + /* + * Restart after every frame to make sure frames haven't + * been added or removed while it was updating. + */ + schedule(); + return Status.OK_STATUS; + } + } + DataTime[] times = descriptor.getFramesInfo().getTimeMap() + .get(WindPlotResource.this); + if (times == null) { + return Status.OK_STATUS; + } + boolean added = false; + for (DataTime time : times) { + if (time == null) { + continue; + } + synchronized (framesLock) { + if (!frames.containsKey(time)) { + addFrame(time); + added = true; + } + } + } + if (added) { + /* + * Restart to ensure data is requested before optimization. + */ + schedule(); + return Status.OK_STATUS; + } + boolean finished = true; + for (DataFrame frame : frameList) { + if (!frame.getRenderable().optimizeDisclosure()) { + finished = false; + } + } + if (!finished) { + schedule(); + } + return Status.OK_STATUS; + } + + } + +} diff --git a/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResourceData.java b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResourceData.java new file mode 100644 index 0000000000..d451e1bf2c --- /dev/null +++ b/cave/com.raytheon.viz.pointdata/src/com/raytheon/viz/pointdata/rsc/wind/WindPlotResourceData.java @@ -0,0 +1,199 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.viz.pointdata.rsc.wind; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.bind.JAXB; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import com.raytheon.uf.common.dataplugin.PluginDataObject; +import com.raytheon.uf.common.dataquery.requests.RequestConstraint; +import com.raytheon.uf.common.dataquery.requests.RequestableMetadataMarshaller; +import com.raytheon.uf.common.localization.IPathManager; +import com.raytheon.uf.common.localization.LocalizationFile; +import com.raytheon.uf.common.localization.PathManagerFactory; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.rsc.AbstractRequestableResourceData; +import com.raytheon.uf.viz.core.rsc.AbstractVizResource; +import com.raytheon.uf.viz.core.rsc.LoadProperties; + +/** + * + * Resource data for displaying wind barbs of point data. + * + *
      + * 
      + * SOFTWARE HISTORY
      + * 
      + * Date          Ticket#  Engineer  Description
      + * ------------- -------- --------- -----------------
      + * Nov 13, 2015  4903     bsteffen  Initial creation
      + * 
      + * 
      + * + * @author bsteffen + * @version 1.0 + */ +@XmlAccessorType(XmlAccessType.NONE) +public class WindPlotResourceData extends AbstractRequestableResourceData { + + @XmlAttribute + private String legend; + + @XmlAttribute + private String windFile; + + @XmlAttribute + private double baseDensity = 1.0; + + @XmlJavaTypeAdapter(value = RequestableMetadataMarshaller.class) + protected HashMap dataMetadataMap; + + @XmlTransient + protected WindPlotConfig config; + + public WindPlotResourceData() { + /* + * Resource data should never instigate a trip to the DB, the resource + * is responsible for requesting its own stuff. + */ + setRetrieveData(false); + setUpdatingOnMetadataOnly(true); + } + + @Override + protected AbstractVizResource constructResource( + LoadProperties loadProperties, PluginDataObject[] objects) + throws VizException { + if (config == null) { + IPathManager pathManager = PathManagerFactory.getPathManager(); + String path = "plotWind" + IPathManager.SEPARATOR + windFile; + LocalizationFile localizedFile = pathManager + .getStaticLocalizationFile(path); + try (InputStream is = localizedFile.openInputStream()) { + config = JAXB.unmarshal(is, WindPlotConfig.class); + } catch (Exception e) { + throw new VizException("Error loading " + path, e); + } + } + return new WindPlotResource(this, loadProperties); + } + + public String getLegend() { + return legend; + } + + public void setLegend(String legend) { + this.legend = legend; + } + + public String getWindFile() { + return windFile; + } + + public void setWindFile(String windFile) { + this.windFile = windFile; + } + + public double getBaseDensity() { + return baseDensity; + } + + public void setBaseDensity(double baseDensity) { + this.baseDensity = baseDensity; + } + + public HashMap getDataMetadataMap() { + return dataMetadataMap; + } + + public void setDataMetadataMap( + HashMap dataMetadataMap) { + this.dataMetadataMap = dataMetadataMap; + } + + public Map getFullDataMetadataMap() { + Map result = new HashMap<>(getMetadataMap()); + if (dataMetadataMap != null) { + result.putAll(dataMetadataMap); + } + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((dataMetadataMap == null) ? 0 : dataMetadataMap.hashCode()); + result = prime * result + ((legend == null) ? 0 : legend.hashCode()); + result = prime * result + + ((windFile == null) ? 0 : windFile.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + WindPlotResourceData other = (WindPlotResourceData) obj; + if (dataMetadataMap == null) { + if (other.dataMetadataMap != null) { + return false; + } + } else if (!dataMetadataMap.equals(other.dataMetadataMap)) { + return false; + } + if (legend == null) { + if (other.legend != null) { + return false; + } + } else if (!legend.equals(other.legend)) { + return false; + } + if (windFile == null) { + if (other.windFile != null) { + return false; + } + } else if (!windFile.equals(other.windFile)) { + return false; + } + return true; + } + + public WindPlotConfig getConfig() { + return config; + } + +} diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/Satellite3_9WindPlots.xml b/cave/com.raytheon.viz.satellite/localization/bundles/Satellite3_9WindPlots.xml index 53153e3770..5f91331f3d 100644 --- a/cave/com.raytheon.viz.satellite/localization/bundles/Satellite3_9WindPlots.xml +++ b/cave/com.raytheon.viz.satellite/localization/bundles/Satellite3_9WindPlots.xml @@ -1,52 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteLayerPlot.xml b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteLayerPlot.xml index c669e5d6be..3da444bea2 100644 --- a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteLayerPlot.xml +++ b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteLayerPlot.xml @@ -1,52 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_0WindPlots.xml b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_0WindPlots.xml index 70094eeb22..248ff3bc22 100644 --- a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_0WindPlots.xml +++ b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_0WindPlots.xml @@ -1,185 +1,136 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_4WindPlots.xml b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_4WindPlots.xml index 6978c1b829..8a7fa984bd 100644 --- a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_4WindPlots.xml +++ b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWV7_4WindPlots.xml @@ -1,211 +1,154 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWindPlots.xml b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWindPlots.xml index 17060a0485..45bb8eeeb5 100644 --- a/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWindPlots.xml +++ b/cave/com.raytheon.viz.satellite/localization/bundles/SatelliteWindPlots.xml @@ -1,237 +1,172 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/bundles/Scatterometer.xml b/cave/com.raytheon.viz.satellite/localization/bundles/Scatterometer.xml new file mode 100644 index 0000000000..a284d1373d --- /dev/null +++ b/cave/com.raytheon.viz.satellite/localization/bundles/Scatterometer.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ascatPlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/ascatPlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ascatPlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/ascatPlotDesign.svg diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/hdwTypePlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/hdwTypePlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/hdwTypePlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/hdwTypePlotDesign.svg diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/hdw_sat_lookup.txt b/cave/com.raytheon.viz.satellite/localization/plotModels/hdw_sat_lookup.txt similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/hdw_sat_lookup.txt rename to cave/com.raytheon.viz.satellite/localization/plotModels/hdw_sat_lookup.txt diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/sat_name_lookup.txt b/cave/com.raytheon.viz.satellite/localization/plotModels/sat_name_lookup.txt similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/sat_name_lookup.txt rename to cave/com.raytheon.viz.satellite/localization/plotModels/sat_name_lookup.txt diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/sat_type_lookup.txt b/cave/com.raytheon.viz.satellite/localization/plotModels/sat_type_lookup.txt similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/sat_type_lookup.txt rename to cave/com.raytheon.viz.satellite/localization/plotModels/sat_type_lookup.txt diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/sat_type_lookup_ascat.txt b/cave/com.raytheon.viz.satellite/localization/plotModels/sat_type_lookup_ascat.txt similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/sat_type_lookup_ascat.txt rename to cave/com.raytheon.viz.satellite/localization/plotModels/sat_type_lookup_ascat.txt diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiPWPlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/ssmiPWPlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiPWPlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/ssmiPWPlotDesign.svg diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiSTPlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/ssmiSTPlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiSTPlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/ssmiSTPlotDesign.svg diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiVILPlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/ssmiVILPlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiVILPlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/ssmiVILPlotDesign.svg diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiVILPlotDesign.txt b/cave/com.raytheon.viz.satellite/localization/plotModels/ssmiVILPlotDesign.txt similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiVILPlotDesign.txt rename to cave/com.raytheon.viz.satellite/localization/plotModels/ssmiVILPlotDesign.txt diff --git a/cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiWindPlotDesign.svg b/cave/com.raytheon.viz.satellite/localization/plotModels/ssmiWindPlotDesign.svg similarity index 100% rename from cave/com.raytheon.viz.pointdata/localization/plotModels/ssmiWindPlotDesign.svg rename to cave/com.raytheon.viz.satellite/localization/plotModels/ssmiWindPlotDesign.svg diff --git a/cave/com.raytheon.viz.satellite/localization/plotWind/ascat_wind.xml b/cave/com.raytheon.viz.satellite/localization/plotWind/ascat_wind.xml new file mode 100644 index 0000000000..7ece70ea3a --- /dev/null +++ b/cave/com.raytheon.viz.satellite/localization/plotWind/ascat_wind.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cave/com.raytheon.viz.satellite/localization/plotWind/hdw_wind.xml b/cave/com.raytheon.viz.satellite/localization/plotWind/hdw_wind.xml new file mode 100644 index 0000000000..2dd21d8b0d --- /dev/null +++ b/cave/com.raytheon.viz.satellite/localization/plotWind/hdw_wind.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file