gov.nasa.worldwind.layers.rpf.RPFTiledImageProcessor Maven / Gradle / Ivy
The newest version!
/*
* 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);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy