src.gov.nasa.worldwind.layers.rpf.RPFTiledImageProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.layers.rpf;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.formats.dds.DDSCompressor;
import gov.nasa.worldwind.formats.nitfs.*;
import gov.nasa.worldwind.formats.rpf.*;
import gov.nasa.worldwind.formats.wvt.WaveletCodec;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.util.*;
import java.awt.*;
import java.awt.image.*;
import java.beans.*;
import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
/**
* @author dcollins
* @version $Id: RPFTiledImageProcessor.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class RPFTiledImageProcessor
{
private int numThreads = -1;
private final PropertyChangeSupport propertyChangeSupport;
private final Object fileLock = new Object();
private volatile boolean doStop = false;
private static final int DEFAULT_WAVELET_SIZE = 256;
public static final String BEGIN_SUB_TASK = "BeginSubTask";
public static final String END_SUB_TASK = "EndSubTask";
public static final String SUB_TASK_NUM_STEPS = "SubTaskNumSteps";
public static final String SUB_TASK_STEP_COMPLETE = "SubTaskStepComplete";
public static final String SUB_TASK_STEP_FAILED = "SubTaskStepFailed";
public RPFTiledImageProcessor()
{
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
public int getThreadPoolSize()
{
return this.numThreads;
}
public void setThreadPoolSize(int size)
{
this.numThreads = size;
}
public RPFFileIndex makeFileIndex(File rootFile, String dataSeriesId, String description,
Iterable fileIterable)
{
if (rootFile == null)
{
String message = Logging.getMessage("nullValue.FileIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (dataSeriesId == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (fileIterable == null)
{
String message = Logging.getMessage("nullValue.IterableIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
RPFFileIndex result = null;
if (!this.doStop)
{
RPFFileIndex fileIndex = new RPFFileIndex();
fileIndex.getIndexProperties().setRootPath(rootFile.getAbsolutePath());
fileIndex.getIndexProperties().setDataSeriesIdentifier(dataSeriesId);
fileIndex.getIndexProperties().setDescription(description);
// Populate the index with the list of RPF files.
for (File file : fileIterable)
{
fileIndex.createRPFFileRecord(file);
}
// Process RPF file records.
int waveletWidth = DEFAULT_WAVELET_SIZE;
int waveletHeight = DEFAULT_WAVELET_SIZE;
processFileIndex(fileIndex, waveletWidth, waveletHeight);
// Update the RPF bounding sector.
fileIndex.updateBoundingSector();
if (!this.doStop)
{
result = fileIndex;
}
}
return result;
}
public Layer makeLayer(RPFFileIndex fileIndex)
{
if (fileIndex == null)
{
String message = "RPFFileIndex is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (fileIndex.getIndexProperties() == null)
{
String message = "RPFFileIndex.IndexProperties is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Layer result = null;
if (!this.doStop)
{
String rootPath = fileIndex.getIndexProperties().getRootPath();
String dataSeriesId = fileIndex.getIndexProperties().getDataSeriesIdentifier();
// Save the RPFFileIndex to the file cache.
File indexFile = WorldWind.getDataFileStore().newFile(RPFTiledImageLayer.getFileIndexCachePath(rootPath, dataSeriesId));
saveFileIndex(fileIndex, indexFile);
// Create tiled imagery.
AVList params = new AVListImpl();
params.setValue(RPFTiledImageLayer.RPF_ROOT_PATH, rootPath);
params.setValue(RPFTiledImageLayer.RPF_DATA_SERIES_ID, dataSeriesId);
params.setValue(RPFGenerator.RPF_FILE_INDEX, fileIndex);
Collection tileList = RPFTiledImageLayer.createTopLevelTiles(params);
RPFGenerator generator = new RPFGenerator(params);
createTiledImagery(tileList, generator);
// Return the layer.
if (!this.doStop)
{
result = new RPFTiledImageLayer(params);
}
}
return result;
}
public void stop()
{
this.doStop = true;
}
private String makeWaveletCachePath(RPFFileIndex fileIndex, long rpfFileKey)
{
String path = null;
if (fileIndex != null && fileIndex.getIndexProperties() != null && rpfFileKey != -1)
{
File rpfFile = fileIndex.getRPFFile(rpfFileKey);
if (rpfFile != null)
{
String rpfFilePath = rpfFile.getPath();
String rootPath = fileIndex.getIndexProperties().getRootPath();
int index = rpfFilePath.lastIndexOf(rootPath);
String partialPath = rpfFilePath.substring(index + rootPath.length(), rpfFilePath.length());
StringBuilder sb = new StringBuilder();
sb.append(WWIO.formPath(
fileIndex.getIndexProperties().getRootPath(),
fileIndex.getIndexProperties().getDataSeriesIdentifier(),
"wavelet"));
sb.append(File.separator);
sb.append(partialPath);
sb.append(WaveletCodec.WVT_EXT);
path = sb.toString();
}
}
return path;
}
private void processFileIndex(final RPFFileIndex fileIndex, final int waveletWidth, final int waveletHeight)
{
RPFFileIndex.Table table = fileIndex.getRPFFileTable();
Collection recordList = table.getRecords();
if (recordList != null)
{
firePropertyChange(BEGIN_SUB_TASK, null, null);
firePropertyChange(SUB_TASK_NUM_STEPS, null, recordList.size());
Collection tasks = new ArrayList();
for (final RPFFileIndex.Record record : recordList)
{
tasks.add(new Runnable() {
public void run() {
File file = fileIndex.getRPFFile(record.getKey());
try {
processRecord(fileIndex, record, waveletWidth, waveletHeight);
firePropertyChange(SUB_TASK_STEP_COMPLETE, null, file.getName());
} catch (Throwable t) {
String message = String.format("Exception while processing file: %s", file);
Logging.logger().log(java.util.logging.Level.SEVERE, message, t);
firePropertyChange(SUB_TASK_STEP_FAILED, null, file.getName());
}
}
});
}
if (this.numThreads > 1)
runAsynchronously(tasks, this.numThreads, true);
else
run(tasks);
firePropertyChange(END_SUB_TASK, null, null);
}
}
private void processRecord(RPFFileIndex fileIndex, RPFFileIndex.Record record,
int waveletWidth, int waveletHeight) throws IOException
{
if (fileIndex == null)
{
String message = "RPFFileIndex is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (record == null)
{
String message = "RPFFileIndex.Record is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
File file = null;
RPFImageFile rpfImageFile = null;
if (!this.doStop)
{
// Load the RPF image file.
file = fileIndex.getRPFFile(record.getKey());
rpfImageFile = RPFImageFile.load(file);
// Create an attribute for the file's sector.
Sector sector = getFileSector(rpfImageFile);
if (sector != null)
{
((RPFFileIndex.RPFFileRecord) record).setSector(sector);
}
}
File waveletFile = null;
if (!this.doStop)
{
// Create the wavelet file path.
synchronized (this.fileLock)
{
String cachePath = makeWaveletCachePath(fileIndex, record.getKey());
waveletFile = WorldWind.getDataFileStore().newFile(cachePath);
}
// Create a record for the wavelet file.
if (waveletFile != null)
{
fileIndex.createWaveletRecord(waveletFile, record.getKey());
}
}
WaveletCodec wavelet = null;
if (!this.doStop)
{
// If the wavelet file is not null, and the source RPF file is newer than the wavelet file,
// then create a new wavelet file.
if (waveletFile != null && (file != null && file.lastModified() > waveletFile.lastModified()))
{
// Get the RPF image file as a BufferedImage.
BufferedImage bi = rpfImageFile.getBufferedImage();
// Must deproject it...
bi = deproject(file, bi);
// Get coverage information from the transform.
// Create the wavelet from the RPF BufferedImage.
if (bi != null)
{
wavelet = createWavelet(bi, waveletWidth, waveletHeight);
//noinspection UnusedAssignment
bi = null;
}
}
//noinspection UnusedAssignment
rpfImageFile = null;
}
if (!this.doStop)
{
// If a wavelet has been created,
// then write the wavelet to file.
if (wavelet != null)
{
ByteBuffer buffer = WaveletCodec.save(wavelet);
if (buffer != null)
{
WWIO.saveBuffer(buffer, waveletFile);
//noinspection UnusedAssignment
buffer = null;
}
//noinspection UnusedAssignment
wavelet = null;
}
}
}
private WaveletCodec createWavelet(BufferedImage image, int waveletWidth, int waveletHeight)
{
int waveletImgType;
switch (image.getType())
{
case BufferedImage.TYPE_BYTE_GRAY:
waveletImgType = BufferedImage.TYPE_BYTE_GRAY;
break;
case BufferedImage.TYPE_INT_BGR:
case BufferedImage.TYPE_INT_RGB:
waveletImgType = BufferedImage.TYPE_3BYTE_BGR;
break;
case BufferedImage.TYPE_INT_ARGB:
waveletImgType = BufferedImage.TYPE_4BYTE_ABGR;
break;
default:
waveletImgType = BufferedImage.TYPE_3BYTE_BGR;
break;
}
BufferedImage scaledImage = new BufferedImage(waveletWidth, waveletHeight, waveletImgType);
scaleImage(image, scaledImage);
return WaveletCodec.encode(scaledImage);
}
private BufferedImage scaleImage(BufferedImage srcImage, BufferedImage destImage)
{
double sx = (double) destImage.getWidth() / (double) srcImage.getWidth();
double sy = (double) destImage.getHeight() / (double) srcImage.getHeight();
Graphics2D g2d = (Graphics2D) destImage.getGraphics();
g2d.scale(sx, sy);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(srcImage, 0, 0, null);
return destImage;
}
private Sector getFileSector(RPFFile rpfFile)
{
// Attempt to get the file's coverage from the RPFFile.
Sector sector = null;
if (rpfFile != null)
{
// We'll first attempt to compute the Sector, if possible, from the filename (if it exists) by using
// the conventions for CADRG and CIB filenames. It has been observed that for polar frame files in
// particular that coverage information in the file itself is sometimes unreliable.
File file = rpfFile.getFile();
if (file != null)
sector = sectorFromFilename(file);
// Can't compute the Sector; see if the RPFFile contains coverage information.
if (sector == null)
sector = sectorFromHeader(rpfFile);
}
return sector;
}
private Sector sectorFromHeader(RPFFile rpfFile)
{
Sector sector = null;
try
{
if (rpfFile != null)
{
NITFSImageSegment imageSegment = (NITFSImageSegment) rpfFile.getNITFSSegment(NITFSSegmentType.IMAGE_SEGMENT);
RPFFrameFileComponents comps = imageSegment.getUserDefinedImageSubheader().getRPFFrameFileComponents();
Angle minLat = comps.swLowerleft.getLatitude();
Angle maxLat = comps.neUpperRight.getLatitude();
Angle minLon = comps.swLowerleft.getLongitude();
Angle maxLon = comps.neUpperRight.getLongitude();
// This sector spans the longitude boundary. In order to render this sector,
// we must adjust the longitudes such that minLon 0)
{
double degrees = 360 + maxLon.degrees;
maxLon = Angle.fromDegrees(degrees);
}
}
sector = new Sector(minLat, maxLat, minLon, maxLon);
}
}
catch (Exception e)
{
// Computing the file's coverage failed. Log the condition and return null.
// This at allows the coverage to be re-computed at a later time.
String message = String.format("Exception while getting file sector: %s", rpfFile != null ? rpfFile.getFile() : "");
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
sector = null;
}
return sector;
}
private Sector sectorFromFilename(File file)
{
Sector sector = null;
try
{
if (file != null && file.getName() != null)
{
// Parse the filename, using the conventions for CADRG and CIB filenames.
RPFFrameFilename rpfFilename = RPFFrameFilename.parseFilename(file.getName().toUpperCase());
// Get the dataseries associated with that code.
RPFDataSeries ds = RPFDataSeries.dataSeriesFor(rpfFilename.getDataSeriesCode());
// Create a transform to compute coverage information.
RPFFrameTransform tx = RPFFrameTransform.createFrameTransform(
rpfFilename.getZoneCode(), ds.rpfDataType, ds.scaleOrGSD);
// Get coverage information from the transform.
sector = tx.computeFrameCoverage(rpfFilename.getFrameNumber());
}
}
catch (Exception e)
{
// Computing the file's coverage failed. Log the condition and return null.
// This at allows the coverage to be re-computed at a later time.
String message = String.format("Exception while computing file sector: %s", file);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
sector = null;
}
return sector;
}
//private boolean isPolarFile(File file)
//{
// boolean isPolar = false;
// try
// {
// if (file != null && file.getName() != null)
// {
// // Parse the filename, using the conventions for CADRG and CIB filenames.
// RPFFrameFilename rpfFilename = RPFFrameFilename.parseFilename(file.getName().toUpperCase());
// // Get the dataseries associated with that code.
// char zoneCode = rpfFilename.getZoneCode();
// // Ignore polar zones.
// if (zoneCode == '9' || zoneCode == 'J')
// isPolar = true;
// }
// }
// catch (Exception e)
// {
// // Computing the file's zone failed.
// isPolar = false;
// }
// return isPolar;
//}
private void createTiledImagery(Collection tileList, RPFGenerator generator)
{
firePropertyChange(BEGIN_SUB_TASK, null, null);
firePropertyChange(SUB_TASK_NUM_STEPS, null, tileList.size());
Collection tasks = new ArrayList();
final RPFGenerator.RPFServiceInstance service = generator.getServiceInstance();
for (final Tile tile : tileList)
{
tasks.add(new Runnable() {
public void run() {
try {
createTileImage(tile, service);
firePropertyChange(SUB_TASK_STEP_COMPLETE, null, tile.getPath());
} catch (Throwable t) {
String message = String.format("Exception while processing image: %s", tile.getPath());
Logging.logger().log(java.util.logging.Level.SEVERE, message, t);
firePropertyChange(SUB_TASK_STEP_FAILED, null, tile.getPath());
}
}
});
}
if (this.numThreads > 1)
runAsynchronously(tasks, this.numThreads, true);
else
run(tasks);
firePropertyChange(END_SUB_TASK, null, null);
}
private void createTileImage(Tile tile, RPFGenerator.RPFServiceInstance service) throws Exception
{
if (tile == null)
{
String message = "Tile is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (service == null)
{
String message = "RPFGenerator.RPFServiceInstance is null";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
File outFile = null;
if (!this.doStop)
{
synchronized (this.fileLock)
{
outFile = WorldWind.getDataFileStore().newFile(tile.getPath());
}
}
BufferedImage image = null;
if (!this.doStop)
{
URL url = tile.getResourceURL();
if (url != null)
{
image = service.serviceRequest(url);
}
}
if (!this.doStop)
{
// If an image has been created,
// then convert it to DDS and write it to file.
if (image != null)
{
ByteBuffer buffer = DDSCompressor.compressImage(image);
if (buffer != null && outFile != null)
{
WWIO.saveBuffer(buffer, outFile);
}
}
}
}
private void saveFileIndex(RPFFileIndex fileIndex, File file)
{
try
{
ByteBuffer buffer = null;
if (fileIndex != null)
{
buffer = fileIndex.save();
}
if (buffer != null && file != null)
{
WWIO.saveBuffer(buffer, file);
}
}
catch (Exception e)
{
String message = String.format("Exception while saving RPFFileIndex: %s", file);
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
}
}
private BufferedImage deproject(File file, BufferedImage image)
{
// Need a RPFFrameTransform object and a frame-number to perform the deprojection...
RPFFrameFilename fframe = RPFFrameFilename.parseFilename(file.getName().toUpperCase());
RPFDataSeries ds = RPFDataSeries.dataSeriesFor(fframe.getDataSeriesCode());
RPFFrameTransform tx = RPFFrameTransform.createFrameTransform(fframe.getZoneCode(),
ds.rpfDataType, ds.scaleOrGSD);
RPFFrameTransform.RPFImage[] images = tx.deproject(fframe.getFrameNumber(), image);
if (images.length == 1)
return images[0].getImage();
// NOTE we are using explicit knowledge of the order of the two images produced in the deprojection step...
BufferedImage westImage = images[0].getImage();
BufferedImage eastImage = images[1].getImage();
BufferedImage outImage = new BufferedImage(westImage.getWidth()+eastImage.getWidth(), westImage.getHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2d = (Graphics2D) outImage.getGraphics();
g2d.drawImage(westImage, 0, 0, null);
g2d.drawImage(eastImage, westImage.getWidth(), 0, null);
return outImage;
}
private void run(Iterable taskIterable)
{
try
{
if (taskIterable != null)
{
for (Runnable task : taskIterable)
{
if (!this.doStop)
{
task.run();
}
}
}
}
catch (Exception e)
{
String message = "Exception while executing tasks";
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
}
}
private void runAsynchronously(Iterable taskIterable, int threadPoolSize, boolean blockUntilFinished)
{
try
{
if (taskIterable != null)
{
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
for (Runnable task : taskIterable)
{
if (!this.doStop)
{
if (task != null)
{
executor.submit(task);
}
}
}
executor.shutdown();
// Attempt to block this thread until all Runnables
// have completed execution.
while (blockUntilFinished && !executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
{}
}
}
catch (Exception e)
{
String message = "Exception while executing tasks";
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
}
}
public void addPropertyChangeListener(PropertyChangeListener listener)
{
this.propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener)
{
this.propertyChangeSupport.removePropertyChangeListener(listener);
}
private void firePropertyChange(String propertyName, Object oldValue, Object newValue)
{
this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
}