Issue #2122 Refactored satellite to use tile set renderable. Deleted old code
Amend: Added fix for issue 2103 Amend: Added software history Change-Id: I1e09bcbf28aaffbf602431a09af50da447e4cf2b Former-commit-id:78c0e1684e
[formerlyb4c0d1f715
] [formerly47b55981c8
] [formerly78c0e1684e
[formerlyb4c0d1f715
] [formerly47b55981c8
] [formerly2f03c38ca0
[formerly47b55981c8
[formerly e98002c1d18eefe039f0bfc9f1b90ef577d42547]]]] Former-commit-id:2f03c38ca0
Former-commit-id:6b4982e8a3
[formerlyc912e8e6c5
] [formerly 81f10786b9755a530e3615de10b091e1f0022d78 [formerlybc4ebb4f08
]] Former-commit-id: d7196e32710e3cc8969f238de14a54ee542ee1fa [formerly20caab72f9
] Former-commit-id:539bbe2cb5
This commit is contained in:
parent
fd6ecf1273
commit
815ab796b9
16 changed files with 1066 additions and 700 deletions
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* 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.uf.viz.core;
|
||||
|
||||
import com.raytheon.uf.viz.core.rsc.hdf5.ImageTile;
|
||||
|
||||
/**
|
||||
* Interface for notification when a mesh calculation has completed
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Jun 19, 2010 mschenke Initial creation
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author mschenke
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
public interface IMeshCallback {
|
||||
|
||||
/**
|
||||
* Notification of when the calculation for the mesh of the tile has been
|
||||
* completed
|
||||
*
|
||||
* @param tile
|
||||
*/
|
||||
public void meshCalculated(ImageTile tile);
|
||||
|
||||
}
|
|
@ -35,7 +35,9 @@ import java.nio.ShortBuffer;
|
|||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Nov 22, 2011 mschenke Initial creation
|
||||
* Nov 22, 2011 mschenke Initial creation
|
||||
* Jun 20, 2013 2122 mschenke Made work with slicing from data with
|
||||
* bounds not starting at 0,0
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -61,7 +63,7 @@ public class BufferSlicer {
|
|||
|| dataBounds.getMinY() < totalBounds.getMinY()
|
||||
|| dataBounds.getMaxX() > totalBounds.getMaxX()
|
||||
|| dataBounds.getMaxY() > totalBounds.getMaxY()) {
|
||||
throw new RuntimeException(
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Data bounds defines region outside of buffer's total bounds");
|
||||
}
|
||||
|
||||
|
@ -111,10 +113,12 @@ public class BufferSlicer {
|
|||
newData = ByteBuffer.allocate(dataSize);
|
||||
}
|
||||
|
||||
int xOffset = (dataBounds.x - totalBounds.x);
|
||||
int yOffset = (dataBounds.y - totalBounds.y);
|
||||
newData.position(0);
|
||||
byte[] bytes = new byte[dataBounds.width];
|
||||
for (int i = 0; i < dataBounds.height; ++i) {
|
||||
data.position((dataBounds.y * totalBounds.width + dataBounds.x) + i
|
||||
data.position((yOffset * totalBounds.width + xOffset) + i
|
||||
* totalBounds.width);
|
||||
data.get(bytes);
|
||||
newData.put(bytes);
|
||||
|
@ -134,10 +138,12 @@ public class BufferSlicer {
|
|||
newData = ShortBuffer.allocate(dataSize);
|
||||
}
|
||||
|
||||
int xOffset = (dataBounds.x - totalBounds.x);
|
||||
int yOffset = (dataBounds.y - totalBounds.y);
|
||||
newData.position(0);
|
||||
short[] shorts = new short[dataBounds.width];
|
||||
for (int i = 0; i < dataBounds.height; ++i) {
|
||||
data.position((dataBounds.y * totalBounds.width + dataBounds.x) + i
|
||||
data.position((yOffset * totalBounds.width + xOffset) + i
|
||||
* totalBounds.width);
|
||||
data.get(shorts);
|
||||
newData.put(shorts);
|
||||
|
@ -157,10 +163,12 @@ public class BufferSlicer {
|
|||
newData = IntBuffer.allocate(dataSize);
|
||||
}
|
||||
|
||||
int xOffset = (dataBounds.x - totalBounds.x);
|
||||
int yOffset = (dataBounds.y - totalBounds.y);
|
||||
newData.position(0);
|
||||
int[] ints = new int[dataBounds.width];
|
||||
for (int i = 0; i < dataBounds.height; ++i) {
|
||||
data.position((dataBounds.y * totalBounds.width + dataBounds.x) + i
|
||||
data.position((yOffset * totalBounds.width + xOffset) + i
|
||||
* totalBounds.width);
|
||||
data.get(ints);
|
||||
newData.put(ints);
|
||||
|
@ -180,10 +188,12 @@ public class BufferSlicer {
|
|||
newData = FloatBuffer.allocate(dataSize);
|
||||
}
|
||||
|
||||
int xOffset = (dataBounds.x - totalBounds.x);
|
||||
int yOffset = (dataBounds.y - totalBounds.y);
|
||||
newData.position(0);
|
||||
float[] floats = new float[dataBounds.width];
|
||||
for (int i = 0; i < dataBounds.height; ++i) {
|
||||
data.position((dataBounds.y * totalBounds.width + dataBounds.x) + i
|
||||
data.position((yOffset * totalBounds.width + xOffset) + i
|
||||
* totalBounds.width);
|
||||
data.get(floats);
|
||||
newData.put(floats);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package com.raytheon.uf.viz.core.rsc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -34,6 +35,7 @@ import com.raytheon.uf.viz.core.drawables.IDescriptor;
|
|||
import com.raytheon.uf.viz.core.drawables.IRenderable;
|
||||
import com.raytheon.uf.viz.core.drawables.PaintProperties;
|
||||
import com.raytheon.uf.viz.core.exception.VizException;
|
||||
import com.raytheon.uf.viz.core.rsc.IResourceDataChanged.ChangeType;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability;
|
||||
|
||||
/**
|
||||
|
@ -45,7 +47,9 @@ import com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability;
|
|||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Dec 21, 2011 mschenke Initial creation
|
||||
* Dec 21, 2011 mschenke Initial creation
|
||||
* Jun 24, 2013 2122 mschenke Made use built in resource data changed listener
|
||||
* updates will not be lost from construction to initInternal
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -66,38 +70,6 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
IRenderable renderable;
|
||||
}
|
||||
|
||||
private IResourceDataChanged dataChangedListener = new IResourceDataChanged() {
|
||||
@Override
|
||||
public void resourceChanged(ChangeType type, Object object) {
|
||||
if (type == ChangeType.DATA_UPDATE) {
|
||||
if (object instanceof PluginDataObject) {
|
||||
addDataObject((PluginDataObject) object);
|
||||
} else if (object instanceof PluginDataObject[]) {
|
||||
addDataObject((PluginDataObject[]) object);
|
||||
} else if (object instanceof Object[]) {
|
||||
List<PluginDataObject> pdos = new ArrayList<PluginDataObject>();
|
||||
for (Object obj : (Object[]) object) {
|
||||
if (obj instanceof PluginDataObject) {
|
||||
pdos.add((PluginDataObject) obj);
|
||||
}
|
||||
}
|
||||
if (pdos.size() > 0) {
|
||||
addDataObject(pdos.toArray(new PluginDataObject[0]));
|
||||
}
|
||||
}
|
||||
} else if (type == ChangeType.CAPABILITY) {
|
||||
if (object instanceof AbstractCapability) {
|
||||
AbstractCapability capability = (AbstractCapability) object;
|
||||
for (Frame frame : frames.values()) {
|
||||
if (frame.renderable != null) {
|
||||
capabilityChanged(frame.renderable, capability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private Map<DataTime, Frame> frames = new HashMap<DataTime, Frame>();
|
||||
|
||||
/**
|
||||
|
@ -110,6 +82,37 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
dataTimes = new ArrayList<DataTime>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resourceDataChanged(ChangeType type, Object object) {
|
||||
if (type == ChangeType.DATA_UPDATE) {
|
||||
if (object instanceof PluginDataObject) {
|
||||
addDataObject((PluginDataObject) object);
|
||||
} else if (object instanceof PluginDataObject[]) {
|
||||
addDataObject((PluginDataObject[]) object);
|
||||
} else if (object instanceof Object[]) {
|
||||
List<PluginDataObject> pdos = new ArrayList<PluginDataObject>();
|
||||
for (Object obj : (Object[]) object) {
|
||||
if (obj instanceof PluginDataObject) {
|
||||
pdos.add((PluginDataObject) obj);
|
||||
}
|
||||
}
|
||||
if (pdos.isEmpty() == false) {
|
||||
addDataObject(pdos.toArray(new PluginDataObject[0]));
|
||||
}
|
||||
}
|
||||
} else if (type == ChangeType.CAPABILITY) {
|
||||
if (object instanceof AbstractCapability) {
|
||||
AbstractCapability capability = (AbstractCapability) object;
|
||||
for (Frame frame : frames.values()) {
|
||||
if (frame.renderable != null) {
|
||||
capabilityChanged(frame.renderable, capability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
issueRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the pdo to the appropriate time and removes any renderable or data
|
||||
* cached for that time.
|
||||
|
@ -153,12 +156,14 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
if (updateRenderable(frame.renderable,
|
||||
pdoList.toArray(new PluginDataObject[0])) == false) {
|
||||
dispose(frame.renderable);
|
||||
frame.renderable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dataTimes.contains(dataTimes)) {
|
||||
dataTimes.add(time);
|
||||
Collections.sort(dataTimes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +210,54 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
return descriptor.getTimeForResource(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the renderable for the frame and creates it if it doesn't exist
|
||||
*
|
||||
* @param dataTime
|
||||
* @return
|
||||
*/
|
||||
protected IRenderable getOrCreateRenderable(DataTime time)
|
||||
throws VizException {
|
||||
Frame frame = null;
|
||||
IRenderable renderable = null;
|
||||
synchronized (this) {
|
||||
frame = frames.get(time);
|
||||
}
|
||||
if (frame != null) {
|
||||
synchronized (frame.lock) {
|
||||
if (!frame.disposed) {
|
||||
renderable = frame.renderable;
|
||||
if (renderable == null) {
|
||||
frame.renderable = renderable = constructRenderable(
|
||||
time, new ArrayList<PluginDataObject>(
|
||||
frame.records));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return renderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the IRenderable for the given time or null if none exists yet
|
||||
*
|
||||
* @param time
|
||||
* @return
|
||||
*/
|
||||
protected IRenderable getRenderable(DataTime time) {
|
||||
Frame frame = null;
|
||||
synchronized (this) {
|
||||
frame = frames.get(time);
|
||||
}
|
||||
if (frame != null) {
|
||||
synchronized (frame.lock) {
|
||||
return frame.renderable;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -260,18 +313,8 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
}
|
||||
|
||||
@Override
|
||||
protected final void initInternal(IGraphicsTarget target)
|
||||
throws VizException {
|
||||
resourceData.addChangeListener(dataChangedListener);
|
||||
initResource(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init method for the resource to initialize any data needed for rendering
|
||||
* which is not tied to a renderable.
|
||||
*/
|
||||
protected void initResource(IGraphicsTarget target) throws VizException {
|
||||
|
||||
protected void initInternal(IGraphicsTarget target) throws VizException {
|
||||
// Default no-op
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -292,25 +335,10 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
return;
|
||||
}
|
||||
}
|
||||
Frame currFrame = null;
|
||||
synchronized (this) {
|
||||
currFrame = frames.get(time);
|
||||
}
|
||||
if (currFrame != null) {
|
||||
synchronized (currFrame.lock) {
|
||||
if (currFrame.disposed == false) {
|
||||
IRenderable renderable = currFrame.renderable;
|
||||
if (renderable == null) {
|
||||
currFrame.renderable = renderable = constructRenderable(
|
||||
time, new ArrayList<PluginDataObject>(
|
||||
currFrame.records));
|
||||
}
|
||||
|
||||
if (renderable != null) {
|
||||
renderable.paint(target, paintProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
IRenderable renderable = getOrCreateRenderable(time);
|
||||
if (renderable != null) {
|
||||
renderable.paint(target, paintProps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,7 +361,6 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
disposeFrame(frame);
|
||||
}
|
||||
|
||||
resourceData.removeChangeListener(dataChangedListener);
|
||||
disposeResource();
|
||||
}
|
||||
|
||||
|
@ -345,12 +372,14 @@ public abstract class AbstractPluginDataObjectResource<T extends AbstractResourc
|
|||
*/
|
||||
private void disposeFrame(Frame frame) {
|
||||
synchronized (frame.lock) {
|
||||
if (frame.renderable != null) {
|
||||
disposeRenderable(frame.renderable);
|
||||
frame.renderable = null;
|
||||
if (!frame.disposed) {
|
||||
if (frame.renderable != null) {
|
||||
dispose(frame.renderable);
|
||||
frame.renderable = null;
|
||||
}
|
||||
frame.records.clear();
|
||||
frame.disposed = true;
|
||||
}
|
||||
frame.records.clear();
|
||||
frame.disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -170,6 +170,10 @@ public abstract class AbstractVizResource<T extends AbstractResourceData, D exte
|
|||
paintListeners = new CopyOnWriteArraySet<IPaintListener>();
|
||||
paintStatusListeners = new CopyOnWriteArraySet<IPaintStatusChangedListener>();
|
||||
disposeListeners = new CopyOnWriteArraySet<IDisposeListener>();
|
||||
|
||||
if (resourceData != null) {
|
||||
resourceData.addChangeListener(changeListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -344,10 +348,6 @@ public abstract class AbstractVizResource<T extends AbstractResourceData, D exte
|
|||
initInternal(target);
|
||||
status = ResourceStatus.INITIALIZED;
|
||||
|
||||
if (resourceData != null) {
|
||||
resourceData.addChangeListener(changeListener);
|
||||
}
|
||||
|
||||
for (IInitListener listener : initListeners) {
|
||||
listener.inited(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,381 @@
|
|||
/**
|
||||
* 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.uf.viz.core.tile;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.geotools.coverage.grid.GridEnvelope2D;
|
||||
import org.geotools.coverage.grid.GridGeometry2D;
|
||||
import org.geotools.geometry.Envelope2D;
|
||||
import org.opengis.coverage.grid.GridEnvelope;
|
||||
|
||||
import com.raytheon.uf.common.colormap.image.ColorMapData;
|
||||
import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.geospatial.ISpatialObject;
|
||||
import com.raytheon.uf.common.geospatial.MapUtil;
|
||||
import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||
import com.raytheon.uf.viz.core.DrawableImage;
|
||||
import com.raytheon.uf.viz.core.HDF5Util;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget.RasterMode;
|
||||
import com.raytheon.uf.viz.core.IMesh;
|
||||
import com.raytheon.uf.viz.core.PixelCoverage;
|
||||
import com.raytheon.uf.viz.core.data.BufferSlicer;
|
||||
import com.raytheon.uf.viz.core.data.IColorMapDataRetrievalCallback;
|
||||
import com.raytheon.uf.viz.core.data.prep.HDF5DataRetriever;
|
||||
import com.raytheon.uf.viz.core.drawables.IColormappedImage;
|
||||
import com.raytheon.uf.viz.core.drawables.IImage.Status;
|
||||
import com.raytheon.uf.viz.core.drawables.ext.colormap.IColormappedImageExtension;
|
||||
import com.raytheon.uf.viz.core.exception.VizException;
|
||||
import com.raytheon.uf.viz.core.map.IMapMeshExtension;
|
||||
import com.raytheon.uf.viz.core.rsc.AbstractVizResource;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.ColorMapCapability;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.ImagingCapability;
|
||||
|
||||
/**
|
||||
* {@link TileSetRenderable} for 2D {@link PluginDataObject}s. Groups adjacent
|
||||
* tiles when retrieving data. Default retrieval mechanism uses
|
||||
* {@link HDF5DataRetriever}. If other mechanism desired, extend class and
|
||||
* override {@link #retrieveRecordData(Tile)}
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Jun 19, 2013 2122 mschenke Initial creation.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author mschenke
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
public class RecordTileSetRenderable extends TileSetRenderable {
|
||||
|
||||
private class RecordTileDataCallback implements
|
||||
IColorMapDataRetrievalCallback {
|
||||
|
||||
private final Tile tile;
|
||||
|
||||
private ColorMapData data;
|
||||
|
||||
private RecordTileDataCallback(Tile tile) {
|
||||
this.tile = tile;
|
||||
}
|
||||
|
||||
private void setRetrievedData(ColorMapData data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorMapData getColorMapData() throws VizException {
|
||||
ColorMapData rval = data;
|
||||
if (rval != null) {
|
||||
// Once retrieved, null out
|
||||
data = null;
|
||||
} else {
|
||||
rval = data = retrieveRecordData(tile);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getOuterType().hashCode();
|
||||
result = prime * result + ((tile == null) ? 0 : tile.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RecordTileDataCallback other = (RecordTileDataCallback) obj;
|
||||
if (!getOuterType().equals(other.getOuterType()))
|
||||
return false;
|
||||
if (tile == null) {
|
||||
if (other.tile != null)
|
||||
return false;
|
||||
} else if (!tile.equals(other.tile))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private RecordTileSetRenderable getOuterType() {
|
||||
// Only compares outer type class to ensure calls to
|
||||
// retrieveRecordData will be the same
|
||||
return RecordTileSetRenderable.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RecordTileImageCreatorTask implements Runnable {
|
||||
|
||||
private final IGraphicsTarget target;
|
||||
|
||||
private final Tile bigTile;
|
||||
|
||||
private final List<Tile> subTiles;
|
||||
|
||||
public RecordTileImageCreatorTask(IGraphicsTarget target, Tile bigTile,
|
||||
List<Tile> subTiles) {
|
||||
this.target = target;
|
||||
this.bigTile = bigTile;
|
||||
this.subTiles = subTiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int numTiles = subTiles.size();
|
||||
int numNeedStaging = 0;
|
||||
|
||||
List<RecordTileDataCallback> callbacks = new ArrayList<RecordTileDataCallback>(
|
||||
numTiles);
|
||||
List<DrawableImage> images = new ArrayList<DrawableImage>(numTiles);
|
||||
|
||||
for (Tile tile : subTiles) {
|
||||
RecordTileDataCallback callback = new RecordTileDataCallback(
|
||||
tile);
|
||||
callbacks.add(callback);
|
||||
DrawableImage image = null;
|
||||
try {
|
||||
image = createTileImage(target, tile, callback);
|
||||
} catch (VizException e) {
|
||||
statusHandler.handle(Priority.PROBLEM,
|
||||
e.getLocalizedMessage(), e);
|
||||
}
|
||||
images.add(image);
|
||||
|
||||
if (image != null
|
||||
&& image.getImage().getStatus() == Status.UNLOADED) {
|
||||
numNeedStaging += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (numNeedStaging == numTiles) {
|
||||
// All the images need staging, do bulk request
|
||||
ColorMapData data = retrieveRecordData(bigTile);
|
||||
|
||||
Rectangle bigTileRect = bigTile.getRectangle();
|
||||
for (int i = 0; i < numTiles; i += 1) {
|
||||
Tile tile = subTiles.get(i);
|
||||
DrawableImage image = images.get(i);
|
||||
if (image != null) {
|
||||
if (image.getImage().getStatus() == Status.UNLOADED) {
|
||||
Rectangle tileRect = tile.getRectangle();
|
||||
ColorMapData subData = new ColorMapData(
|
||||
BufferSlicer.slice(data.getBuffer(),
|
||||
tileRect, bigTileRect), new int[] {
|
||||
tileRect.width, tileRect.height },
|
||||
data.getDataType());
|
||||
|
||||
callbacks.get(i).setRetrievedData(subData);
|
||||
try {
|
||||
image.getImage().stage();
|
||||
} catch (VizException e) {
|
||||
statusHandler.handle(Priority.PROBLEM,
|
||||
e.getLocalizedMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numTiles; i += 1) {
|
||||
Tile tile = subTiles.get(i);
|
||||
DrawableImage image = images.get(i);
|
||||
addTileImage(tile, image);
|
||||
}
|
||||
target.setNeedsRefresh(true);
|
||||
}
|
||||
|
||||
public DrawableImage createTileImage(IGraphicsTarget target, Tile tile,
|
||||
RecordTileDataCallback callback) throws VizException {
|
||||
IColormappedImage image = target.getExtension(
|
||||
IColormappedImageExtension.class).initializeRaster(
|
||||
callback, colormapping.getColorMapParameters());
|
||||
IMesh mesh = target.getExtension(IMapMeshExtension.class)
|
||||
.constructMesh(tile.tileGeometry,
|
||||
tileSet.getTargetGeometry());
|
||||
return new DrawableImage(image, new PixelCoverage(mesh),
|
||||
RasterMode.ASYNCHRONOUS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final ColorMapCapability colormapping;
|
||||
|
||||
protected final PluginDataObject record;
|
||||
|
||||
public RecordTileSetRenderable(AbstractVizResource<?, ?> resource,
|
||||
PluginDataObject record, ISpatialObject spatialObject,
|
||||
int tileLevels) {
|
||||
this(resource, record, MapUtil.getGridGeometry(spatialObject),
|
||||
tileLevels);
|
||||
}
|
||||
|
||||
public RecordTileSetRenderable(AbstractVizResource<?, ?> resource,
|
||||
PluginDataObject record, GridGeometry2D tileSetGeometry,
|
||||
int tileLevels) {
|
||||
this(resource, record, tileSetGeometry, tileLevels, 512);
|
||||
}
|
||||
|
||||
public RecordTileSetRenderable(AbstractVizResource<?, ?> resource,
|
||||
PluginDataObject record, GridGeometry2D tileSetGeometry,
|
||||
int tileLevels, int tileSize) {
|
||||
super(resource.getCapability(ImagingCapability.class), tileSetGeometry,
|
||||
null, tileLevels, tileSize);
|
||||
this.record = record;
|
||||
this.colormapping = resource.getCapability(ColorMapCapability.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createTileImages(IGraphicsTarget target,
|
||||
Collection<Tile> tilesToCreate) {
|
||||
Map<Integer, List<Tile>> mapped = new HashMap<Integer, List<Tile>>();
|
||||
for (Tile tile : tilesToCreate) {
|
||||
// Ensure no job already scheduled for tile
|
||||
if (jobMap.get(tile) == null) {
|
||||
List<Tile> tiles = mapped.get(tile.tileLevel);
|
||||
if (tiles == null) {
|
||||
tiles = new ArrayList<Tile>();
|
||||
mapped.put(tile.tileLevel, tiles);
|
||||
}
|
||||
tiles.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
for (Integer tileLevel : mapped.keySet()) {
|
||||
List<Tile> tiles = mapped.get(tileLevel);
|
||||
List<GridEnvelope2D> rectangles = new ArrayList<GridEnvelope2D>();
|
||||
List<Envelope2D> envelopes = new ArrayList<Envelope2D>();
|
||||
|
||||
for (Tile tile : tiles) {
|
||||
rectangles.add(tile.tileGeometry.getGridRange2D());
|
||||
envelopes.add(tile.tileGeometry.getEnvelope2D());
|
||||
}
|
||||
|
||||
// Join together any adjacent rectangles
|
||||
for (int i = 0; i < rectangles.size(); i++) {
|
||||
Rectangle r1 = rectangles.get(i);
|
||||
Envelope2D e1 = envelopes.get(i);
|
||||
for (int j = i + 1; j < rectangles.size(); j++) {
|
||||
Rectangle r2 = rectangles.get(j);
|
||||
Envelope2D e2 = envelopes.get(j);
|
||||
boolean joinable = true;
|
||||
if (r1.x == r2.x && r1.width == r2.width) {
|
||||
if (r1.getMaxY() == r2.getMinY()
|
||||
|| r1.getMinY() == r2.getMaxY()) {
|
||||
joinable = true;
|
||||
}
|
||||
} else if (r1.y == r2.y && r1.height == r2.height) {
|
||||
if (r1.getMaxX() == r2.getMinX()
|
||||
|| r1.getMinX() == r2.getMaxX()) {
|
||||
joinable = true;
|
||||
}
|
||||
}
|
||||
if (joinable) {
|
||||
// Join the rectangles
|
||||
rectangles.remove(r1);
|
||||
rectangles.remove(r2);
|
||||
Rectangle2D joined = new GridEnvelope2D();
|
||||
Rectangle2D.union(r1, r2, joined);
|
||||
rectangles.add((GridEnvelope2D) joined);
|
||||
|
||||
// Join the envelopes
|
||||
envelopes.remove(e1);
|
||||
envelopes.remove(e2);
|
||||
joined = new Envelope2D();
|
||||
Rectangle2D.union(e1, e2, joined);
|
||||
envelopes.add((Envelope2D) joined);
|
||||
|
||||
// start all over.
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start a separate job for every big rectangle
|
||||
for (int i = 0; i < rectangles.size(); i++) {
|
||||
Tile joinedTile = new Tile(tileLevel, new GridGeometry2D(
|
||||
(GridEnvelope) rectangles.get(i), envelopes.get(i)));
|
||||
RecordTileImageCreatorTask task = new RecordTileImageCreatorTask(
|
||||
target, joinedTile, tiles);
|
||||
for (Tile tile : tiles) {
|
||||
jobMap.put(tile, task);
|
||||
}
|
||||
tileCreationPool.schedule(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tile
|
||||
* @return
|
||||
*/
|
||||
protected ColorMapData retrieveRecordData(Tile tile) {
|
||||
return new HDF5DataRetriever(HDF5Util.findHDF5Location(record),
|
||||
DataStoreFactory.createDataSetName(record.getDataURI(),
|
||||
DataStoreFactory.DEF_DATASET_NAME, tile.tileLevel),
|
||||
tile.getRectangle()).getColorMapData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((record == null) ? 0 : record.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RecordTileSetRenderable other = (RecordTileSetRenderable) obj;
|
||||
if (record == null) {
|
||||
if (other.record != null)
|
||||
return false;
|
||||
} else if (!record.equals(other.record))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ package com.raytheon.uf.viz.core.tile;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -63,7 +64,9 @@ import com.vividsolutions.jts.geom.Coordinate;
|
|||
* ------------ ---------- ----------- --------------------------
|
||||
* Aug 8, 2012 mschenke Initial creation
|
||||
* May 28, 2013 2037 njensen Made imageMap concurrent to fix leak
|
||||
*
|
||||
* Jun 20, 2013 2122 mschenke Fixed null pointer in interrogate and made
|
||||
* canceling jobs safer
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author mschenke
|
||||
|
@ -297,7 +300,21 @@ public class TileSetRenderable implements IRenderable {
|
|||
lastPaintedLevel = usedTileLevel;
|
||||
|
||||
return getImagesWithinExtent(target, paintProps.getView().getExtent(),
|
||||
usedTileLevel, 0);
|
||||
usedTileLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the images to render within the extent in target grid space
|
||||
*
|
||||
* @param target
|
||||
* @param extent
|
||||
* @param level
|
||||
* @return
|
||||
* @throws VizException
|
||||
*/
|
||||
protected List<DrawableImage> getImagesWithinExtent(IGraphicsTarget target,
|
||||
IExtent extent, int level) {
|
||||
return getImagesWithinExtent(target, extent, level, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,8 +326,8 @@ public class TileSetRenderable implements IRenderable {
|
|||
* @return
|
||||
* @throws VizException
|
||||
*/
|
||||
protected List<DrawableImage> getImagesWithinExtent(IGraphicsTarget target,
|
||||
IExtent extent, int level, int depth) throws VizException {
|
||||
private List<DrawableImage> getImagesWithinExtent(IGraphicsTarget target,
|
||||
IExtent extent, int level, int depth) {
|
||||
if (tileSet == null) {
|
||||
// Early exit condition, disposed or haven't projected yet
|
||||
return Collections.emptyList();
|
||||
|
@ -366,10 +383,13 @@ public class TileSetRenderable implements IRenderable {
|
|||
// All intersecting tiles are loaded for this level, cancel any
|
||||
// jobs running for tiles we don't need anymore (may be case if
|
||||
// zooming or panning)
|
||||
for (Runnable job : jobMap.values()) {
|
||||
tileCreationPool.cancel(job);
|
||||
Iterator<Runnable> iterator = jobMap.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Runnable job = iterator.next();
|
||||
if (tileCreationPool.cancel(job)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
jobMap.clear();
|
||||
} else {
|
||||
target.setNeedsRefresh(true);
|
||||
// Create tiles needing images
|
||||
|
@ -446,12 +466,15 @@ public class TileSetRenderable implements IRenderable {
|
|||
TileLevel level = tileSet.getTileLevel(lastPaintedLevel);
|
||||
double[] grid = level.crsToGrid(localX, localY);
|
||||
Tile tile = level.getTile(grid[0], grid[1]);
|
||||
DrawableImage di = imageMap.get(tile);
|
||||
if (di != null) {
|
||||
IImage image = di.getImage();
|
||||
if (image instanceof IColormappedImage) {
|
||||
return ((IColormappedImage) image).getValue((int) grid[0]
|
||||
% tileSize, (int) grid[1] % tileSize);
|
||||
if (tile != null) {
|
||||
DrawableImage di = imageMap.get(tile);
|
||||
if (di != null) {
|
||||
IImage image = di.getImage();
|
||||
if (image instanceof IColormappedImage) {
|
||||
return ((IColormappedImage) image).getValue(
|
||||
(int) grid[0] % tileSize, (int) grid[1]
|
||||
% tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TransformException e) {
|
||||
|
|
|
@ -24,10 +24,8 @@ import java.awt.Rectangle;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.collections.keyvalue.MultiKey;
|
||||
|
@ -53,7 +51,6 @@ import com.raytheon.uf.viz.core.DrawableImage;
|
|||
import com.raytheon.uf.viz.core.IExtent;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget.RasterMode;
|
||||
import com.raytheon.uf.viz.core.IMeshCallback;
|
||||
import com.raytheon.uf.viz.core.PixelCoverage;
|
||||
import com.raytheon.uf.viz.core.VizApp;
|
||||
import com.raytheon.uf.viz.core.drawables.IColormappedImage;
|
||||
|
@ -83,13 +80,14 @@ import com.vividsolutions.jts.geom.Coordinate;
|
|||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Feb 15, 2007 chammack Initial Creation.
|
||||
* Jun 24, 2013 2122 mschenke Removed unused IMeshCallback listeners
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author chammack
|
||||
* @version 1
|
||||
*/
|
||||
public abstract class AbstractTileSet implements IRenderable, IMeshCallback {
|
||||
public abstract class AbstractTileSet implements IRenderable {
|
||||
private static final transient IUFStatusHandler statusHandler = UFStatus
|
||||
.getHandler(AbstractTileSet.class);
|
||||
|
||||
|
@ -147,8 +145,6 @@ public abstract class AbstractTileSet implements IRenderable, IMeshCallback {
|
|||
|
||||
private boolean disposed = true;
|
||||
|
||||
private Set<IMeshCallback> meshCallbacks = new HashSet<IMeshCallback>();
|
||||
|
||||
public AbstractTileSet(int levels, int tileSize,
|
||||
GridGeometry2D gridGeometry, AbstractVizResource<?, ?> rsc,
|
||||
PixelInCell pixelOrientation, String viewType) throws VizException {
|
||||
|
@ -905,18 +901,4 @@ public abstract class AbstractTileSet implements IRenderable, IMeshCallback {
|
|||
return jobMap.isEmpty();
|
||||
}
|
||||
|
||||
public void addMeshCallback(IMeshCallback meshCallback) {
|
||||
this.meshCallbacks.add(meshCallback);
|
||||
}
|
||||
|
||||
public void removeMeshCallback(IMeshCallback meshCallback) {
|
||||
this.meshCallbacks.remove(meshCallback);
|
||||
}
|
||||
|
||||
public void meshCalculated(ImageTile tile) {
|
||||
for (IMeshCallback meshCallback : meshCallbacks) {
|
||||
meshCallback.meshCalculated(tile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,254 +0,0 @@
|
|||
/**
|
||||
* 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.core.rsc.hdf5;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import javax.measure.converter.UnitConverter;
|
||||
|
||||
import org.geotools.coverage.grid.GridGeometry2D;
|
||||
import org.opengis.referencing.datum.PixelInCell;
|
||||
|
||||
import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.IDataStore;
|
||||
import com.raytheon.uf.common.datastorage.Request;
|
||||
import com.raytheon.uf.common.datastorage.StorageException;
|
||||
import com.raytheon.uf.common.datastorage.records.FloatDataRecord;
|
||||
import com.raytheon.uf.common.datastorage.records.IDataRecord;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget;
|
||||
import com.raytheon.uf.viz.core.data.IDataPreparer;
|
||||
import com.raytheon.uf.viz.core.data.prep.CMDataPreparerManager;
|
||||
import com.raytheon.uf.viz.core.datastructure.DataCubeContainer;
|
||||
import com.raytheon.uf.viz.core.datastructure.VizDataCubeException;
|
||||
import com.raytheon.uf.viz.core.drawables.IImage;
|
||||
import com.raytheon.uf.viz.core.exception.VizException;
|
||||
import com.raytheon.uf.viz.core.rsc.AbstractVizResource;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.ColorMapCapability;
|
||||
|
||||
/**
|
||||
* Memory based tileset.
|
||||
*
|
||||
* This memory-based tileset pulls a small raster from an hdf5 file and
|
||||
* interpolates it to a larger size. The raster is then split once it is already
|
||||
* loaded in memory.git pull
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Feb 15, 2007 chammack Initial Creation.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author chammack
|
||||
* @version 1
|
||||
*/
|
||||
public class MemoryBasedTileSet extends AbstractTileSet {
|
||||
|
||||
protected Object[] loadedData;
|
||||
|
||||
protected int[][] dims;
|
||||
|
||||
protected boolean[] isLoaded;
|
||||
|
||||
protected UnitConverter converter;
|
||||
|
||||
protected PluginDataObject pdo;
|
||||
|
||||
protected String hdf5File;
|
||||
|
||||
protected String group;
|
||||
|
||||
protected String dataset;
|
||||
|
||||
public MemoryBasedTileSet(String hdf5File, String group, String dataset,
|
||||
AbstractTileSet sharedGeometryTileset, UnitConverter converter,
|
||||
PluginDataObject pdo) throws VizException {
|
||||
super(sharedGeometryTileset);
|
||||
this.converter = converter;
|
||||
this.isLoaded = new boolean[sharedGeometryTileset.levels];
|
||||
this.pdo = pdo;
|
||||
this.hdf5File = hdf5File;
|
||||
this.group = group;
|
||||
this.dataset = dataset;
|
||||
}
|
||||
|
||||
public MemoryBasedTileSet(String hdf5File, String group, String dataset,
|
||||
int levels, int tileSize, GridGeometry2D gridGeometry,
|
||||
AbstractVizResource<?, ?> rsc, UnitConverter converter,
|
||||
PixelInCell orientation, PluginDataObject pdo, String viewType)
|
||||
throws VizException {
|
||||
super(levels, tileSize, gridGeometry, rsc, orientation, viewType);
|
||||
this.converter = converter;
|
||||
this.isLoaded = new boolean[levels];
|
||||
this.pdo = pdo;
|
||||
this.hdf5File = hdf5File;
|
||||
this.group = group;
|
||||
this.dataset = dataset;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* com.raytheon.viz.core.rsc.tiling.AbstractTileSet#preloadDataObject(int)
|
||||
*/
|
||||
@Override
|
||||
protected void preloadDataObject(int level) throws StorageException {
|
||||
synchronized (isLoaded) {
|
||||
if (isLoaded[level]) {
|
||||
return;
|
||||
}
|
||||
IDataRecord rec = getDataRecord();
|
||||
|
||||
if (loadedData == null) {
|
||||
loadedData = new Object[levels];
|
||||
dims = new int[levels][];
|
||||
}
|
||||
|
||||
if (rec != null) {
|
||||
|
||||
loadedData[level] = rec.getDataObject();
|
||||
|
||||
long[] d = rec.getSizes();
|
||||
dims[level] = new int[] { (int) d[0], (int) d[1] };
|
||||
|
||||
}
|
||||
|
||||
isLoaded[level] = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected IDataRecord getDataRecord() throws StorageException {
|
||||
if (pdo != null) {
|
||||
try {
|
||||
IDataRecord[] records = DataCubeContainer.getDataRecord(pdo);
|
||||
if (records != null && records.length > 0) {
|
||||
return records[0];
|
||||
}
|
||||
} catch (VizDataCubeException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
IDataStore ds = DataStoreFactory
|
||||
.getDataStore(new File(hdf5File));
|
||||
return ds.retrieve("", group + "/" + dataset, Request.ALL);
|
||||
// (float) this.interpolationFactorOnLoad
|
||||
// * (float) (Math.pow(2, this.levels
|
||||
// - level - 1)));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new StorageException("Unable to open file", null, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* com.raytheon.viz.core.rsc.tiling.AbstractTileSet#createTile(com.raytheon
|
||||
* .viz.core.IGraphicsTarget, int, int, int)
|
||||
*/
|
||||
@Override
|
||||
protected IImage createTile(IGraphicsTarget target, int level, int i, int j)
|
||||
throws VizException {
|
||||
IDataPreparer preparer = CMDataPreparerManager.getDataPreparer(
|
||||
loadedData[level], this.tileSet.getTile(level, i, j)
|
||||
.getRectangle(), dims[level]);
|
||||
return target.initializeRaster(preparer,
|
||||
rsc.getCapability(ColorMapCapability.class)
|
||||
.getColorMapParameters());
|
||||
}
|
||||
|
||||
public float getDataMin() {
|
||||
float dataMin = Float.POSITIVE_INFINITY;
|
||||
|
||||
if (loadedData[0] instanceof float[]) {
|
||||
float[] floatData = (float[]) loadedData[0];
|
||||
for (int i = 0; i < floatData.length; i++) {
|
||||
if (!Float.isNaN(floatData[i]) && floatData[i] != -999999) {
|
||||
dataMin = Math.min(dataMin, floatData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataMin;
|
||||
|
||||
}
|
||||
|
||||
public float getDataMax() {
|
||||
float dataMax = Float.NEGATIVE_INFINITY;
|
||||
|
||||
if (loadedData[0] instanceof float[]) {
|
||||
float[] floatData = (float[]) loadedData[0];
|
||||
for (int i = 0; i < floatData.length; i++) {
|
||||
if (!Float.isNaN(floatData[i]) && floatData[i] != -999999) {
|
||||
dataMax = Math.max(dataMax, floatData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataMax;
|
||||
|
||||
}
|
||||
|
||||
protected IDataRecord getFullDataSet() throws StorageException,
|
||||
FileNotFoundException {
|
||||
|
||||
IDataStore ds = DataStoreFactory.getDataStore(new File(hdf5File));
|
||||
IDataRecord dr = ds.retrieve(group, dataset, Request.ALL);
|
||||
|
||||
if (converter == null) {
|
||||
return dr;
|
||||
}
|
||||
|
||||
if (dr instanceof FloatDataRecord) {
|
||||
float[] fd = ((FloatDataRecord) dr).getFloatData();
|
||||
for (int i = 0; i < fd.length; i++) {
|
||||
fd[i] = (float) converter.convert(fd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return dr;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* com.raytheon.viz.core.rsc.tiling.AbstractTileSet#hasDataPreloaded(int)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasDataPreloaded(int level) {
|
||||
return this.isLoaded[level];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelRequest(int level, int i, int j) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -53,7 +53,7 @@ import com.raytheon.uf.viz.core.datastructure.VizDataCubeException;
|
|||
* @author mschenke
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public class SatDataRetriever implements IColorMapDataRetrievalCallback {
|
||||
private static final transient IUFStatusHandler statusHandler = UFStatus
|
||||
.getHandler(SatDataRetriever.class);
|
||||
|
|
|
@ -50,7 +50,10 @@ import com.raytheon.viz.core.rsc.hdf5.AbstractTileSet;
|
|||
import com.raytheon.viz.core.rsc.hdf5.CreateTileJob;
|
||||
import com.raytheon.viz.core.rsc.hdf5.FileBasedTileSet;
|
||||
import com.raytheon.viz.satellite.data.prep.SatDataRetriever;
|
||||
import com.raytheon.viz.satellite.tileset.SatTileSetRenderable;
|
||||
|
||||
/** Use {@link SatTileSetRenderable} instead */
|
||||
@Deprecated
|
||||
public class SatFileBasedTileSet extends FileBasedTileSet {
|
||||
private static final transient IUFStatusHandler statusHandler = UFStatus
|
||||
.getHandler(SatFileBasedTileSet.class);
|
||||
|
|
|
@ -23,8 +23,8 @@ import java.text.ParseException;
|
|||
import java.text.ParsePosition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -33,9 +33,7 @@ import javax.measure.converter.UnitConverter;
|
|||
import javax.measure.unit.Unit;
|
||||
import javax.measure.unit.UnitFormat;
|
||||
|
||||
import org.opengis.coverage.grid.GridGeometry;
|
||||
import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
||||
import org.opengis.referencing.datum.PixelInCell;
|
||||
|
||||
import com.raytheon.uf.common.colormap.IColorMap;
|
||||
import com.raytheon.uf.common.colormap.prefs.ColorMapParameters;
|
||||
|
@ -47,26 +45,23 @@ import com.raytheon.uf.common.dataplugin.satellite.units.counts.DerivedWVPixel;
|
|||
import com.raytheon.uf.common.dataplugin.satellite.units.generic.GenericPixel;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.units.goes.PolarPrecipWaterPixel;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.units.water.BlendedTPWPixel;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.geospatial.ISpatialObject;
|
||||
import com.raytheon.uf.common.geospatial.MapUtil;
|
||||
import com.raytheon.uf.common.geospatial.ReferencedCoordinate;
|
||||
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.BinOffset;
|
||||
import com.raytheon.uf.common.time.DataTime;
|
||||
import com.raytheon.uf.viz.core.DrawableImage;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget;
|
||||
import com.raytheon.uf.viz.core.IMeshCallback;
|
||||
import com.raytheon.uf.viz.core.drawables.ColorMapLoader;
|
||||
import com.raytheon.uf.viz.core.drawables.IRenderable;
|
||||
import com.raytheon.uf.viz.core.drawables.PaintProperties;
|
||||
import com.raytheon.uf.viz.core.exception.VizException;
|
||||
import com.raytheon.uf.viz.core.map.MapDescriptor;
|
||||
import com.raytheon.uf.viz.core.rsc.AbstractVizResource;
|
||||
import com.raytheon.uf.viz.core.rsc.IResourceDataChanged;
|
||||
import com.raytheon.uf.viz.core.map.IMapDescriptor;
|
||||
import com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource;
|
||||
import com.raytheon.uf.viz.core.rsc.LoadProperties;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.ColorMapCapability;
|
||||
import com.raytheon.uf.viz.core.rsc.hdf5.ImageTile;
|
||||
import com.raytheon.uf.viz.core.style.ParamLevelMatchCriteria;
|
||||
import com.raytheon.uf.viz.core.style.StyleManager;
|
||||
import com.raytheon.uf.viz.core.style.StyleRule;
|
||||
|
@ -74,11 +69,11 @@ import com.raytheon.uf.viz.core.style.level.Level;
|
|||
import com.raytheon.uf.viz.core.style.level.SingleLevel;
|
||||
import com.raytheon.uf.viz.derivparam.library.DerivedParameterRequest;
|
||||
import com.raytheon.viz.core.drawables.ColorMapParameterFactory;
|
||||
import com.raytheon.viz.core.rsc.hdf5.AbstractTileSet;
|
||||
import com.raytheon.viz.core.rsc.hdf5.FileBasedTileSet;
|
||||
import com.raytheon.viz.core.style.image.ImagePreferences;
|
||||
import com.raytheon.viz.core.style.image.SamplePreferences;
|
||||
import com.raytheon.viz.satellite.SatelliteConstants;
|
||||
import com.raytheon.viz.satellite.tileset.SatTileSetRenderable;
|
||||
import com.vividsolutions.jts.geom.Coordinate;
|
||||
|
||||
/**
|
||||
* Provides satellite raster rendering support
|
||||
|
@ -93,49 +88,151 @@ import com.raytheon.viz.satellite.SatelliteConstants;
|
|||
* 02/17/2009 njensen Refactored to new rsc architecture.
|
||||
* 03/02/2009 2032 jsanchez Added check for displayedDate if no data.
|
||||
* 03/25/2009 2086 jsanchez Mapped correct converter to parameter type.
|
||||
* Updated the call to ColormapParametersFactory.build
|
||||
* Updated the call to ColormapParametersFactory.build
|
||||
* 03/30/2009 2169 jsanchez Updated numLevels handling.
|
||||
* - AWIPS2 Baseline Repository --------
|
||||
* 07/17/2012 798 jkorman Use decimationLevels from SatelliteRecord. Removed hard-coded
|
||||
* data set names.
|
||||
* 07/17/2012 798 jkorman Use decimationLevels from SatelliteRecord. Removed hard-coded
|
||||
* data set names.
|
||||
* 06/20/2013 2122 mschenke Modified to use SatTileSetRenderable
|
||||
* </pre>
|
||||
*
|
||||
* @author chammack
|
||||
* @version 1
|
||||
*/
|
||||
public class SatResource extends
|
||||
AbstractVizResource<SatResourceData, MapDescriptor> implements
|
||||
IResourceDataChanged, IMeshCallback {
|
||||
|
||||
public static String RAW_VALUE = "rawValue";
|
||||
AbstractPluginDataObjectResource<SatResourceData, IMapDescriptor> {
|
||||
|
||||
private static final transient IUFStatusHandler statusHandler = UFStatus
|
||||
.getHandler(SatResource.class);
|
||||
|
||||
protected Map<DataTime, SatFileBasedTileSet> tileSet;
|
||||
public static String RAW_VALUE = "rawValue";
|
||||
|
||||
private Map<DataTime, SatelliteRecord> recordMap = new HashMap<DataTime, SatelliteRecord>();
|
||||
private static class InterrogationResult {
|
||||
|
||||
protected DataTime displayedDate;
|
||||
private final SatelliteRecord record;
|
||||
|
||||
protected SatFileBasedTileSet baseTile;
|
||||
private final double value;
|
||||
|
||||
public InterrogationResult(SatelliteRecord record, double value) {
|
||||
this.record = record;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public SatelliteRecord getRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SatRenderable implements IRenderable {
|
||||
|
||||
private Map<ISpatialObject, SatTileSetRenderable> tileMap = new HashMap<ISpatialObject, SatTileSetRenderable>();
|
||||
|
||||
private DataTime renderableTime;
|
||||
|
||||
public SatRenderable(DataTime renderableTime) {
|
||||
this.renderableTime = renderableTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(IGraphicsTarget target, PaintProperties paintProps)
|
||||
throws VizException {
|
||||
Collection<DrawableImage> images = getImagesToRender(target,
|
||||
paintProps);
|
||||
if (images.isEmpty() == false) {
|
||||
target.drawRasters(paintProps,
|
||||
images.toArray(new DrawableImage[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<DrawableImage> getImagesToRender(
|
||||
IGraphicsTarget target, PaintProperties paintProps)
|
||||
throws VizException {
|
||||
List<DrawableImage> images = new ArrayList<DrawableImage>();
|
||||
synchronized (tileMap) {
|
||||
for (SatTileSetRenderable renderable : tileMap.values()) {
|
||||
images.addAll(renderable.getImagesToRender(target,
|
||||
paintProps));
|
||||
}
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
public void addRecord(SatelliteRecord record) {
|
||||
synchronized (tileMap) {
|
||||
SatTileSetRenderable tileSet = tileMap.get(record
|
||||
.getSpatialObject());
|
||||
if (tileSet != null) {
|
||||
SatelliteRecord existingRecord = tileSet
|
||||
.getSatelliteRecord();
|
||||
if (existingRecord.equals(record) == false) {
|
||||
// Different record, same spatial area for same frame
|
||||
// Determine if new one is better than existing
|
||||
long existingTimeMillis = existingRecord.getDataTime()
|
||||
.getMatchRef();
|
||||
long newRecordTimeMillis = record.getDataTime()
|
||||
.getMatchRef();
|
||||
long normalTimeMillis = renderableTime.getMatchRef();
|
||||
if (Math.abs(normalTimeMillis - newRecordTimeMillis) < Math
|
||||
.abs(normalTimeMillis - existingTimeMillis)) {
|
||||
// New is better since it's data time is closer to
|
||||
// the normal time than the existing record's time!
|
||||
tileSet.dispose();
|
||||
tileSet = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tileSet == null) {
|
||||
tileSet = new SatTileSetRenderable(SatResource.this, record);
|
||||
tileSet.project(descriptor.getGridGeometry());
|
||||
tileMap.put(record.getSpatialObject(), tileSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void project() {
|
||||
synchronized (tileMap) {
|
||||
for (SatTileSetRenderable renderable : tileMap.values()) {
|
||||
renderable.project(descriptor.getGridGeometry());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
synchronized (tileMap) {
|
||||
for (SatTileSetRenderable renderable : tileMap.values()) {
|
||||
renderable.dispose();
|
||||
}
|
||||
tileMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public InterrogationResult interrogate(Coordinate latLon)
|
||||
throws VizException {
|
||||
InterrogationResult result = null;
|
||||
synchronized (tileMap) {
|
||||
for (SatTileSetRenderable renderable : tileMap.values()) {
|
||||
double rValue = renderable.interrogate(latLon);
|
||||
if (Double.isNaN(rValue) == false && rValue != fillValue) {
|
||||
result = new InterrogationResult(
|
||||
renderable.getSatelliteRecord(), rValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected String legend;
|
||||
|
||||
protected IGraphicsTarget target;
|
||||
|
||||
protected boolean hasBeenInited;
|
||||
|
||||
protected int numLevels;
|
||||
|
||||
protected String viewType;
|
||||
|
||||
protected SatFileBasedTileSet currentTile;
|
||||
|
||||
protected GridGeometry recordGeometry;
|
||||
|
||||
protected SamplePreferences sampleRange;
|
||||
|
||||
protected double fillValue = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -143,28 +240,27 @@ public class SatResource extends
|
|||
*/
|
||||
public SatResource(SatResourceData data, LoadProperties props) {
|
||||
super(data, props);
|
||||
data.addChangeListener(this);
|
||||
this.tileSet = new HashMap<DataTime, SatFileBasedTileSet>();
|
||||
this.dataTimes = new ArrayList<DataTime>();
|
||||
this.legend = null;
|
||||
SatelliteRecord[] records = data.getRecords();
|
||||
Arrays.sort(records, new SatelliteRecordComparator());
|
||||
addDataObject(data.getRecords());
|
||||
}
|
||||
|
||||
for (SatelliteRecord record : records) {
|
||||
@Override
|
||||
protected DataTime getDataObjectTime(PluginDataObject pdo) {
|
||||
SatelliteRecord record = (SatelliteRecord) pdo;
|
||||
if (dataTimes.isEmpty()) {
|
||||
try {
|
||||
addRecord(record);
|
||||
initializeFirstFrame(record);
|
||||
} catch (VizException e) {
|
||||
statusHandler.handle(Priority.PROBLEM,
|
||||
"Error adding satellite record", e);
|
||||
statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This handles if there is no data for East & West CONUS simultaneously
|
||||
*/
|
||||
if (dataTimes.size() > 1) {
|
||||
displayedDate = dataTimes.get(dataTimes.size() - 1);
|
||||
DataTime pdoTime = pdo.getDataTime();
|
||||
if (resourceData.getBinOffset() != null) {
|
||||
pdoTime = resourceData.getBinOffset().getNormalizedTime(pdoTime);
|
||||
}
|
||||
|
||||
return pdoTime;
|
||||
}
|
||||
|
||||
private void initializeFirstFrame(SatelliteRecord record)
|
||||
|
@ -219,6 +315,7 @@ public class SatResource extends
|
|||
cmName = colorMapParameters.getColorMapName();
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the sampleRange from the preferences
|
||||
ParamLevelMatchCriteria match = new ParamLevelMatchCriteria();
|
||||
match.setParameterName(Arrays.asList(physicalElement));
|
||||
|
@ -229,17 +326,16 @@ public class SatResource extends
|
|||
if (sr != null && sr.getPreferences() instanceof ImagePreferences) {
|
||||
sampleRange = ((ImagePreferences) sr.getPreferences())
|
||||
.getSamplePrefs();
|
||||
String lg = ((ImagePreferences) sr.getPreferences())
|
||||
.getLegend();
|
||||
String lg = ((ImagePreferences) sr.getPreferences()).getLegend();
|
||||
// test, so legend is not over written with empty string
|
||||
if (lg != null && !lg.trim().isEmpty()) {
|
||||
legend = lg;
|
||||
}
|
||||
}
|
||||
|
||||
colorMapParameters = ColorMapParameterFactory.build(null,
|
||||
record.getDataURI() + "/Data", physicalElement, unit, level,
|
||||
creatingEntity);
|
||||
colorMapParameters = ColorMapParameterFactory.build((Object) null,
|
||||
physicalElement, unit, level, creatingEntity);
|
||||
// TODO: Figure out data/color map min/max values better
|
||||
if (unit == null) {
|
||||
colorMapParameters.setColorMapMin(0.0f);
|
||||
colorMapParameters.setColorMapMax(255.0f);
|
||||
|
@ -259,18 +355,21 @@ public class SatResource extends
|
|||
colorMapParameters.setDataMax(255.0f);
|
||||
}
|
||||
|
||||
if (colorMap == null) {
|
||||
if (cmName == null) {
|
||||
cmName = "Sat/VIS/ZA (Vis Default)";
|
||||
}
|
||||
colorMap = ColorMapLoader.loadColorMap(cmName);
|
||||
}
|
||||
|
||||
if (colorMap != null) {
|
||||
colorMapParameters.setColorMap(colorMap);
|
||||
}
|
||||
if (cmName != null) {
|
||||
colorMapParameters.setColorMapName(cmName);
|
||||
}
|
||||
|
||||
getCapability(ColorMapCapability.class).setColorMapParameters(
|
||||
colorMapParameters);
|
||||
|
||||
// number of interpolation levels plus the base level!
|
||||
numLevels = record.getInterpolationLevels() + 1;
|
||||
this.legend = getLegend(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -281,110 +380,32 @@ public class SatResource extends
|
|||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disposeInternal() {
|
||||
if (baseTile != null)
|
||||
baseTile.dispose();
|
||||
for (AbstractTileSet tile : this.tileSet.values())
|
||||
if (tile != baseTile)
|
||||
tile.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initInternal(IGraphicsTarget target) throws VizException {
|
||||
synchronized (this) {
|
||||
this.viewType = target.getViewType();
|
||||
this.hasBeenInited = true;
|
||||
this.target = target;
|
||||
|
||||
if (this.baseTile != null) {
|
||||
this.baseTile.init(target);
|
||||
}
|
||||
for (AbstractTileSet tile : this.tileSet.values()) {
|
||||
if (tile != baseTile) {
|
||||
tile.init(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColorMapParameters params = getCapability(ColorMapCapability.class)
|
||||
.getColorMapParameters();
|
||||
if (params.getColorMap() == null) {
|
||||
String colorMapName = params.getColorMapName();
|
||||
if (colorMapName == null)
|
||||
colorMapName = "Sat/VIS/ZA (Vis Default)";
|
||||
|
||||
params.setColorMap(target.buildColorMap(colorMapName));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @seecom.raytheon.viz.core.rsc.IVizResource#paint(com.raytheon.viz.core.
|
||||
* IGraphicsTarget, com.raytheon.viz.core.PixelExtent, double, float)
|
||||
*/
|
||||
@Override
|
||||
protected void paintInternal(IGraphicsTarget target,
|
||||
PaintProperties paintProps) throws VizException {
|
||||
this.target = target;
|
||||
this.displayedDate = paintProps.getDataTime();
|
||||
if (this.displayedDate == null)
|
||||
return;
|
||||
|
||||
currentTile = this.tileSet.get(this.displayedDate);
|
||||
if (currentTile != null) {
|
||||
currentTile.paint(target, paintProps);
|
||||
}
|
||||
// System.out.println("Time to paint: "
|
||||
// + (System.currentTimeMillis() - t0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescriptor(MapDescriptor descriptor) {
|
||||
if (this.baseTile != null) {
|
||||
this.baseTile.setMapDescriptor(descriptor);
|
||||
}
|
||||
for (AbstractTileSet tile : this.tileSet.values())
|
||||
tile.setMapDescriptor(descriptor);
|
||||
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void project(CoordinateReferenceSystem mapData) throws VizException {
|
||||
if (this.baseTile != null)
|
||||
this.baseTile.reproject();
|
||||
|
||||
for (AbstractTileSet tile : tileSet.values())
|
||||
tile.reproject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> interrogate(ReferencedCoordinate coord)
|
||||
throws VizException {
|
||||
DataTime displayedDate = descriptor.getFramesInfo().getTimeForResource(
|
||||
this);
|
||||
Map<String, Object> dataMap = new HashMap<String, Object>();
|
||||
SatelliteRecord record = recordMap.get(displayedDate);
|
||||
if (record != null) {
|
||||
dataMap.put(ISpatialObject.class.toString(),
|
||||
record.getSpatialObject());
|
||||
AbstractTileSet tile = tileSet.get(displayedDate);
|
||||
if (tile != null) {
|
||||
try {
|
||||
Double raw = tile.interrogate(coord.asLatLon(), true);
|
||||
if (raw != 0 && raw.isNaN() == false) {
|
||||
UnitConverter dataToDisplay = getCapability(
|
||||
ColorMapCapability.class)
|
||||
.getColorMapParameters()
|
||||
.getDataToDisplayConverter();
|
||||
if (dataToDisplay != null) {
|
||||
raw = dataToDisplay.convert(raw);
|
||||
}
|
||||
dataMap.put(RAW_VALUE, raw);
|
||||
|
||||
SatRenderable renderable = (SatRenderable) getRenderable(displayedDate);
|
||||
if (renderable != null) {
|
||||
try {
|
||||
InterrogationResult result = renderable.interrogate(coord
|
||||
.asLatLon());
|
||||
if (result != null) {
|
||||
double dataValue = result.getValue();
|
||||
UnitConverter dataToDisplay = getCapability(
|
||||
ColorMapCapability.class).getColorMapParameters()
|
||||
.getDataToDisplayConverter();
|
||||
if (dataToDisplay != null) {
|
||||
dataValue = dataToDisplay.convert(dataValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VizException("Error interrogating raw data", e);
|
||||
dataMap.put(RAW_VALUE, dataValue);
|
||||
dataMap.put(ISpatialObject.class.toString(), result
|
||||
.getRecord().getSpatialObject());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new VizException("Error interrogating raw data", e);
|
||||
}
|
||||
}
|
||||
return dataMap;
|
||||
|
@ -434,116 +455,7 @@ public class SatResource extends
|
|||
return String.format("%.1f%s", value, unitString);
|
||||
}
|
||||
|
||||
public class SatelliteRecordComparator implements
|
||||
Comparator<SatelliteRecord> {
|
||||
|
||||
public int compare(SatelliteRecord o1, SatelliteRecord o2) {
|
||||
return o1.getDataTime().getRefTime()
|
||||
.compareTo(o2.getDataTime().getRefTime());
|
||||
}
|
||||
}
|
||||
|
||||
public void addRecord(PluginDataObject record) throws VizException {
|
||||
synchronized (this) {
|
||||
SatFileBasedTileSet tile;
|
||||
DataTime recordTime = null;
|
||||
if (resourceData.getBinOffset() != null || resourceData.equals(0)) {
|
||||
BinOffset binOffset = resourceData.getBinOffset();
|
||||
DataTime recTime = record.getDataTime();
|
||||
DataTime normTime = binOffset.getNormalizedTime(recTime);
|
||||
if (recordMap.containsKey(normTime)) {
|
||||
// a normalized time was found...
|
||||
// if this record's time is closer than the existing
|
||||
// record's
|
||||
// then replace the existing record with the new record
|
||||
SatelliteRecord satRec = recordMap.get(normTime);
|
||||
DataTime existingTime = satRec.getDataTime();
|
||||
long existingTimeMillis = existingTime
|
||||
.getRefTimeAsCalendar().getTimeInMillis();
|
||||
long recTimeMillies = recTime.getRefTimeAsCalendar()
|
||||
.getTimeInMillis();
|
||||
long normTimeMillies = normTime.getRefTimeAsCalendar()
|
||||
.getTimeInMillis();
|
||||
if (Math.abs(normTimeMillies - existingTimeMillis) > Math
|
||||
.abs(normTimeMillies - recTimeMillies)) {
|
||||
// System.out.println("For " + normTime +
|
||||
// "\n\treplaced "
|
||||
// + existingTime + "\n\twith " + recTime);
|
||||
recordMap.remove(normTime);
|
||||
FileBasedTileSet oldTile = tileSet.remove(normTime);
|
||||
if (oldTile != null) {
|
||||
oldTile.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
recordTime = normTime;
|
||||
} else {
|
||||
recordTime = record.getDataTime();
|
||||
}
|
||||
recordMap.put(recordTime, (SatelliteRecord) record);
|
||||
if (baseTile == null) {
|
||||
initializeFirstFrame((SatelliteRecord) record);
|
||||
}
|
||||
|
||||
if (baseTile == null) {
|
||||
tile = baseTile = new SatFileBasedTileSet(record, DataStoreFactory.DEF_DATASET_NAME,
|
||||
numLevels, 256,
|
||||
MapUtil.getGridGeometry(((SatelliteRecord) record)
|
||||
.getSpatialObject()), this,
|
||||
PixelInCell.CELL_CORNER, viewType);
|
||||
} else {
|
||||
tile = new SatFileBasedTileSet(record, DataStoreFactory.DEF_DATASET_NAME, baseTile);
|
||||
}
|
||||
tile.addMeshCallback(this);
|
||||
tile.setMapDescriptor(this.descriptor);
|
||||
if (hasBeenInited)
|
||||
tile.init(target);
|
||||
|
||||
if (this.legend == null) {
|
||||
this.legend = getLegend(record);
|
||||
}
|
||||
FileBasedTileSet oldTile = tileSet.put(recordTime, tile);
|
||||
if (oldTile != null) {
|
||||
oldTile.dispose();
|
||||
}
|
||||
dataTimes.add(recordTime);
|
||||
|
||||
Collections.sort(this.dataTimes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(DataTime dataTime) {
|
||||
synchronized (this) {
|
||||
if (!this.dataTimes.contains(dataTime))
|
||||
return;
|
||||
|
||||
this.dataTimes.remove(dataTime);
|
||||
|
||||
FileBasedTileSet tile = tileSet.remove(dataTime);
|
||||
if (tile != baseTile && tile != null)
|
||||
tile.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceChanged(ChangeType type, Object object) {
|
||||
if (type.equals(ChangeType.DATA_UPDATE)) {
|
||||
PluginDataObject[] pdos = (PluginDataObject[]) object;
|
||||
for (PluginDataObject pdo : pdos) {
|
||||
try {
|
||||
this.addRecord(pdo);
|
||||
} catch (VizException e) {
|
||||
statusHandler.handle(Priority.PROBLEM,
|
||||
"Error updating satellite resource", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
issueRefresh();
|
||||
}
|
||||
|
||||
private String getLegend(PluginDataObject record) {
|
||||
|
||||
String productName = null;
|
||||
DerivedParameterRequest request = (DerivedParameterRequest) record
|
||||
.getMessageData();
|
||||
|
@ -556,23 +468,87 @@ public class SatResource extends
|
|||
((SatelliteRecord) record).getCreatingEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void meshCalculated(ImageTile tile) {
|
||||
issueRefresh();
|
||||
}
|
||||
|
||||
public List<DrawableImage> getImages(IGraphicsTarget target,
|
||||
PaintProperties paintProps) throws VizException {
|
||||
this.target = target;
|
||||
this.displayedDate = paintProps.getDataTime();
|
||||
if (this.displayedDate == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
currentTile = this.tileSet.get(this.displayedDate);
|
||||
if (currentTile != null) {
|
||||
return currentTile.getImages(target, paintProps);
|
||||
SatRenderable renderable = (SatRenderable) getOrCreateRenderable(paintProps
|
||||
.getDataTime());
|
||||
if (renderable != null) {
|
||||
return new ArrayList<DrawableImage>(renderable.getImagesToRender(
|
||||
target, paintProps));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource#
|
||||
* capabilityChanged(com.raytheon.uf.viz.core.drawables.IRenderable,
|
||||
* com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability)
|
||||
*/
|
||||
@Override
|
||||
protected void capabilityChanged(IRenderable renderable,
|
||||
AbstractCapability capability) {
|
||||
issueRefresh();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource#
|
||||
* disposeRenderable(com.raytheon.uf.viz.core.drawables.IRenderable)
|
||||
*/
|
||||
@Override
|
||||
protected void disposeRenderable(IRenderable renderable) {
|
||||
((SatRenderable) renderable).dispose();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource#
|
||||
* projectRenderable(com.raytheon.uf.viz.core.drawables.IRenderable,
|
||||
* org.opengis.referencing.crs.CoordinateReferenceSystem)
|
||||
*/
|
||||
@Override
|
||||
protected boolean projectRenderable(IRenderable renderable,
|
||||
CoordinateReferenceSystem crs) throws VizException {
|
||||
((SatRenderable) renderable).project();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource#
|
||||
* constructRenderable(com.raytheon.uf.common.time.DataTime, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected IRenderable constructRenderable(DataTime time,
|
||||
List<PluginDataObject> records) throws VizException {
|
||||
SatRenderable renderable = new SatRenderable(time);
|
||||
updateRenderable(renderable, records.toArray(new PluginDataObject[0]));
|
||||
renderable.project();
|
||||
return renderable;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.rsc.AbstractPluginDataObjectResource#
|
||||
* updateRenderable(com.raytheon.uf.viz.core.drawables.IRenderable,
|
||||
* com.raytheon.uf.common.dataplugin.PluginDataObject[])
|
||||
*/
|
||||
@Override
|
||||
protected boolean updateRenderable(IRenderable renderable,
|
||||
PluginDataObject... pdos) {
|
||||
SatRenderable sr = (SatRenderable) renderable;
|
||||
for (PluginDataObject object : pdos) {
|
||||
if (object instanceof SatelliteRecord) {
|
||||
sr.addRecord((SatelliteRecord) object);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* 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.satellite.tileset;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import com.raytheon.uf.common.colormap.image.ColorMapData;
|
||||
import com.raytheon.uf.common.colormap.image.ColorMapData.ColorMapDataType;
|
||||
import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.Request;
|
||||
import com.raytheon.uf.common.datastorage.records.ByteDataRecord;
|
||||
import com.raytheon.uf.common.datastorage.records.IDataRecord;
|
||||
import com.raytheon.uf.common.datastorage.records.ShortDataRecord;
|
||||
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.viz.core.data.IColorMapDataRetrievalCallback;
|
||||
import com.raytheon.uf.viz.core.datastructure.DataCubeContainer;
|
||||
import com.raytheon.uf.viz.core.datastructure.VizDataCubeException;
|
||||
|
||||
/**
|
||||
* {@link IColorMapDataRetrievalCallback} for satellite imagery data. Supports
|
||||
* signed and unsigned byte and short data
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Jun 20, 2013 2122 mschenke Initial creation
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author mschenke
|
||||
* @version 1.0
|
||||
*/
|
||||
public class SatDataRetriever implements IColorMapDataRetrievalCallback {
|
||||
private static final transient IUFStatusHandler statusHandler = UFStatus
|
||||
.getHandler(SatDataRetriever.class);
|
||||
|
||||
protected Rectangle datasetBounds;
|
||||
|
||||
protected PluginDataObject pdo;
|
||||
|
||||
protected String dataset;
|
||||
|
||||
protected boolean signed = false;
|
||||
|
||||
public SatDataRetriever(PluginDataObject pdo, int level,
|
||||
Rectangle dataSetBounds, boolean signed) {
|
||||
this.pdo = pdo;
|
||||
this.datasetBounds = dataSetBounds;
|
||||
dataset = DataStoreFactory.createDataSetName(null,
|
||||
SatelliteRecord.SAT_DATASET_NAME, level);
|
||||
this.signed = signed;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see com.raytheon.uf.viz.core.data.IDataRetrievalCallback#getData()
|
||||
*/
|
||||
@Override
|
||||
public ColorMapData getColorMapData() {
|
||||
// TODO: Read scale/offset out of attributes?
|
||||
Buffer data = null;
|
||||
Request req = Request.buildSlab(new int[] { this.datasetBounds.x,
|
||||
this.datasetBounds.y }, new int[] {
|
||||
this.datasetBounds.x + this.datasetBounds.width,
|
||||
this.datasetBounds.y + this.datasetBounds.height });
|
||||
IDataRecord[] dataRecord = null;
|
||||
try {
|
||||
dataRecord = DataCubeContainer
|
||||
.getDataRecord(pdo, req, this.dataset);
|
||||
if (dataRecord != null && dataRecord.length == 1) {
|
||||
IDataRecord record = dataRecord[0];
|
||||
if (record instanceof ByteDataRecord) {
|
||||
data = ByteBuffer.wrap((byte[]) record.getDataObject());
|
||||
} else if (record instanceof ShortDataRecord) {
|
||||
data = ShortBuffer.wrap((short[]) record.getDataObject());
|
||||
}
|
||||
}
|
||||
} catch (VizDataCubeException e) {
|
||||
statusHandler.handle(Priority.SIGNIFICANT,
|
||||
"Error retrieving satellite data", e);
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ColorMapDataType dataType = null;
|
||||
if (data instanceof ByteBuffer) {
|
||||
dataType = signed ? ColorMapDataType.SIGNED_BYTE
|
||||
: ColorMapDataType.BYTE;
|
||||
} else if (data instanceof ShortBuffer) {
|
||||
dataType = signed ? ColorMapDataType.SHORT
|
||||
: ColorMapDataType.UNSIGNED_SHORT;
|
||||
} else {
|
||||
dataType = ColorMapData.getDataType(data);
|
||||
}
|
||||
|
||||
return new ColorMapData(data, new int[] { datasetBounds.width,
|
||||
datasetBounds.height }, dataType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((dataset == null) ? 0 : dataset.hashCode());
|
||||
result = prime * result
|
||||
+ ((datasetBounds == null) ? 0 : datasetBounds.hashCode());
|
||||
result = prime * result + ((pdo == null) ? 0 : pdo.hashCode());
|
||||
result = prime * result + (signed ? 1231 : 1237);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
SatDataRetriever other = (SatDataRetriever) obj;
|
||||
if (dataset == null) {
|
||||
if (other.dataset != null)
|
||||
return false;
|
||||
} else if (!dataset.equals(other.dataset))
|
||||
return false;
|
||||
if (datasetBounds == null) {
|
||||
if (other.datasetBounds != null)
|
||||
return false;
|
||||
} else if (!datasetBounds.equals(other.datasetBounds))
|
||||
return false;
|
||||
if (pdo == null) {
|
||||
if (other.pdo != null)
|
||||
return false;
|
||||
} else if (!pdo.equals(other.pdo))
|
||||
return false;
|
||||
if (signed != other.signed)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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.satellite.tileset;
|
||||
|
||||
import com.raytheon.uf.common.colormap.image.ColorMapData;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.units.generic.GenericPixel;
|
||||
import com.raytheon.uf.viz.core.rsc.AbstractVizResource;
|
||||
import com.raytheon.uf.viz.core.rsc.capabilities.ColorMapCapability;
|
||||
import com.raytheon.uf.viz.core.tile.RecordTileSetRenderable;
|
||||
import com.raytheon.uf.viz.core.tile.Tile;
|
||||
|
||||
/**
|
||||
* Satellite tile set renderable, uses {@link SatDataRetriever} for {@link Tile}
|
||||
* data retrieval
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
*
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Jun 19, 2013 mschenke Initial creation
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author mschenke
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
public class SatTileSetRenderable extends RecordTileSetRenderable {
|
||||
|
||||
private final boolean signed;
|
||||
|
||||
private final AbstractVizResource<?,?> resource;
|
||||
|
||||
/**
|
||||
* Create satellite tile set renderable
|
||||
*
|
||||
* @param resource
|
||||
* @param record
|
||||
* @param signed
|
||||
*/
|
||||
public SatTileSetRenderable(AbstractVizResource<?,?> resource, SatelliteRecord record) {
|
||||
// Total levels = Number of interpolation levels + base level
|
||||
super(resource, record, record.getSpatialObject(), record
|
||||
.getInterpolationLevels() + 1);
|
||||
this.resource = resource;
|
||||
// TODO: Better way of determining this (taken from SatFileBasedTileSet)
|
||||
this.signed = resource.getCapability(ColorMapCapability.class)
|
||||
.getColorMapParameters().getDataUnit() instanceof GenericPixel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ColorMapData retrieveRecordData(Tile tile) {
|
||||
ColorMapData data = new SatDataRetriever(record, tile.tileLevel,
|
||||
tile.getRectangle(), signed).getColorMapData();
|
||||
resource.issueRefresh();
|
||||
return data;
|
||||
}
|
||||
|
||||
public SatelliteRecord getSatelliteRecord() {
|
||||
return (SatelliteRecord) record;
|
||||
}
|
||||
}
|
|
@ -29,5 +29,6 @@ insert into awips.satellite_units values (27,'SounderCloudTopHeightPixel');
|
|||
insert into awips.satellite_units values (28,'SounderCloudAmountPixel');
|
||||
insert into awips.satellite_units values (29,'RainfallRatePixel');
|
||||
insert into awips.satellite_units values (43,'IRPixel');
|
||||
insert into awips.satellite_units values (48,'IRPixel');
|
||||
insert into awips.satellite_units values (60,'PercentOfNormalTPWPixel');
|
||||
insert into awips.satellite_units values (64,'IRPixel');
|
||||
|
|
|
@ -97,7 +97,7 @@ public class ColorMapData {
|
|||
return dataType;
|
||||
}
|
||||
|
||||
private static ColorMapData.ColorMapDataType getDataType(Buffer buffer) {
|
||||
public static ColorMapData.ColorMapDataType getDataType(Buffer buffer) {
|
||||
if (buffer instanceof FloatBuffer) {
|
||||
return ColorMapData.ColorMapDataType.FLOAT;
|
||||
} else if (buffer instanceof IntBuffer) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import javax.measure.quantity.Dimensionless;
|
|||
import javax.measure.quantity.Length;
|
||||
import javax.measure.quantity.Temperature;
|
||||
import javax.measure.quantity.Velocity;
|
||||
import javax.measure.unit.SI;
|
||||
import javax.measure.unit.Unit;
|
||||
import javax.measure.unit.UnitFormat;
|
||||
|
||||
|
@ -49,6 +50,7 @@ import com.raytheon.uf.common.dataplugin.satellite.units.water.RainfallRatePixel
|
|||
* Sep 4, 2007 njensen Initial creation
|
||||
* Mar 23, 2009 2086 jsanchez Updated RainfallRatePixel to be velocity.
|
||||
* Added PolarPrecipWaterPixel.
|
||||
* Jun 20, 2013 2122 mschenke Added alias for degrees celsius to "C"
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -83,8 +85,8 @@ public class SatelliteUnits {
|
|||
public static final Unit<Dimensionless> GENERIC_PIXEL = new GenericPixel();
|
||||
|
||||
public static void register() {
|
||||
UnitFormat.getUCUMInstance().alias(SI.CELSIUS, "C");
|
||||
UnitFormat.getUCUMInstance().label(SatelliteUnits.IR_PIXEL, "IRPixel");
|
||||
|
||||
UnitFormat.getUCUMInstance().label(SatelliteUnits.PRECIP_PIXEL,
|
||||
"PrecipPixel");
|
||||
UnitFormat.getUCUMInstance().label(SatelliteUnits.RAINFALL_RATE_PIXEL,
|
||||
|
|
Loading…
Add table
Reference in a new issue