// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/dataAccess/asrp/ASRPDirectory.java,v $
// $RCSfile: ASRPDirectory.java,v $
// $Revision: 1.8 $
// $Date: 2005/12/09 21:09:15 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.dataAccess.asrp;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.List;
import com.bbn.openmap.dataAccess.iso8211.DDFField;
import com.bbn.openmap.dataAccess.iso8211.DDFModule;
import com.bbn.openmap.dataAccess.iso8211.DDFSubfield;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMRect;
import com.bbn.openmap.omGraphics.OMScalingRaster;
import com.bbn.openmap.proj.EqualArc;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.cacheHandler.CacheHandler;
import com.bbn.openmap.util.cacheHandler.CacheObject;
/**
* An ASRP directory contains information needed to view images. It contains
* multiple files, each containing complementary information about the image.
* The GeneralInformationFile (GEN) contains information about the image such as
* coverage and location. The QualityFile (QAL) contains accuracy and color
* information. The GeoReferenceFile (GER) contains projection information, the
* SourceFile (SOU) contains information about the map that was used to create
* the images. The RasterGeoDataFile (IMG) contains the actual pixel
* information.
* <P>
*
* This class knows how to use all of these files to create images, which are
* made up of subframe tiles called blocks.
*/
public class ASRPDirectory extends CacheHandler implements ASRPConstants {
protected GeneralInformationFile gen;
protected QualityFile qal;
protected RasterGeoDataFile img;
protected GeoReferenceFile ger;
protected SourceFile sou;
/** List of tile indexes. */
protected List tsi;
/** Number of horizontal blocks. */
protected int numHorBlocks_N;
/** Number of vertical blocks. */
protected int numVerBlocks_M;
/** Number of horizontal pixels per block. */
protected int numHorPixels_Q;
/** Number of vertical pixels per block. */
protected int numVerPixels_P;
/**
* When reading image bytes, the number of bits that represent the number of
* pixels the next color index stands for.
*/
protected int pixelCountBits;
/**
* When reading image bytes, the number of bits that represent the color
* index.
*/
protected int pixelValueBits;
/* Bounding coordinates for coverage. */
protected float swo, nea, neo, swa; // west lon, north lat, east
// lon, south lat
/* Upper left latitude/longitude for top left tile. */
protected float lso, pso; // padded longitude, latitude of upper
// left image corner
/** Number of pixels 360 degrees east - west. */
protected int arv;
/** Number of pixels 360 degrees north - south. */
protected int brv;
/**
* Calculated number of degrees per block in the horizontal direction.
*/
protected float degPerHorBlock;
/**
* Calculated number of degrees per block in the vertical direction.
*/
protected float degPerVerBlock;
/** Byte offset into the IMG file where tile data starts. */
protected int tileDataOffset;
/** The colors from the QAL file. */
protected Color[] colors;
/** The OMRect used to track coverage boundaries. */
protected OMRect bounds;
protected File dir;
/**
* Protective mechanism, doesn't display data that has images with a base
* scale that is more than a factor of the scaleFactor away from the scale
* of the map.
*/
protected double scaleFactor = 4;
/**
* Create a new ASRP directory for the given path. Calls initialize() which
* will read in the different files to find out the attribute information
* about the data.
*/
public ASRPDirectory(String path) {
dir = new File(path);
if (dir.exists()) {
try {
initialize(dir.getPath(), dir.getName(), "01");
} catch (IOException ioe) {
Debug.error(ioe.getMessage());
ioe.printStackTrace();
return;
}
} else {
Debug.error("ASRPDirectory (" + path + ") doesn't exist");
}
}
public String getPath() {
if (dir != null) {
return dir.getPath();
}
return null;
}
/**
* Get the OMRect used for calculating coverage area.
*/
public OMRect getBounds() {
if (bounds == null) {
bounds = new OMRect(pso, lso, pso - degPerVerBlock * numVerBlocks_M, lso
+ degPerHorBlock * numHorBlocks_N, OMGraphic.LINETYPE_GREATCIRCLE);
}
return bounds;
}
public void setScaleFactor(double scaleFactorIn) {
scaleFactor = scaleFactorIn;
}
public double getScaleFactor() {
return scaleFactor;
}
/**
* Return true of current bounds covers the projection area.
*/
public boolean isOnMap(Projection proj) {
OMRect bds = getBounds();
bds.generate(proj);
Shape s = bds.getShape();
return s.intersects(0, 0, proj.getWidth(), proj.getHeight());
}
public boolean validScale(Projection proj) {
if (proj instanceof EqualArc) {
EqualArc ea = (EqualArc) proj;
double xPixConstant = ea.getXPixConstant();
double scale = xPixConstant / arv;
boolean result = (scale < scaleFactor) && (scale > 1 / scaleFactor);
if (Debug.debugging("asrp")) {
Debug.output("Scale comparing arv = " + arv + ", "
+ xPixConstant + ", result: " + result);
}
return result;
}
return false;
}
/**
* Get an OMGraphicList of files that cover the projection. Returns an empty
* list if the coverage isn't over the map.
*/
public OMGraphicList checkProjAndGetTiledImages(Projection proj)
throws IOException {
if (!isOnMap(proj) || !validScale(proj)) {
// off the map
return new OMGraphicList();
}
return getTiledImages(proj);
}
/**
* Assumes that the projection checks have occurred, have passed, and just
* fetches the image tiles.
*/
public OMGraphicList getTiledImages(Projection proj) throws IOException {
float ullat = pso;
float ullon = lso;
float lrlat = ullat - (degPerVerBlock * numVerBlocks_M);
float lrlon = ullon + (degPerHorBlock * numHorBlocks_N);
Point2D llp1 = proj.getUpperLeft();
Point2D llp2 = proj.getLowerRight();
int startX = (int) Math.floor((llp1.getX() - ullon) / degPerHorBlock);
int startY = (int) Math.floor((ullat - llp1.getY()) / degPerVerBlock);
int endX = numHorBlocks_N
- (int) Math.floor((lrlon - llp2.getX()) / degPerHorBlock);
int endY = numVerBlocks_M
- (int) Math.floor((llp2.getY() - lrlat) / degPerVerBlock);
if (startX < 0)
startX = 0;
if (startY < 0)
startY = 0;
if (endX > numHorBlocks_N)
endX = numHorBlocks_N;
if (endY > numVerBlocks_M)
endY = numVerBlocks_M;
return getTiledImages(new Rectangle(startX, startY, endX - startX, endY
- startY), proj);
}
/**
* Provide an OMGraphicList containing the tile blocks described by the
* rectangle.
*
* @param rect rectangle defining the tile blocks to get. rect.x and rect.y
* describe the starting upper left block to get, rect.getWidth and
* rect.getHeight describe the number of tiles to the right and down
* from the first block to collect.
*/
protected OMGraphicList getTiledImages(Rectangle rect, Projection proj)
throws IOException {
if (Debug.debugging("asrp")) {
Debug.output("ASRPDirectory: fielding request for " + rect);
}
OMGraphicList list = new OMGraphicList();
int startX = (int) rect.getX();
int startY = (int) rect.getY();
int endX = startX + (int) rect.getWidth();
int endY = startY + (int) rect.getHeight();
for (int x = startX; x < endX; x++) {
for (int y = startY; y < endY; y++) {
OMGraphic omg = (OMGraphic) get(new String(x + "," + y).intern());
if (omg != null) {
omg.generate(proj);
list.add(omg);
}
}
}
return list;
}
/**
* Fetch the subframe tile block from the IMG file.
*/
public OMScalingRaster getBlock(int x, int y) throws IOException {
float ullat = pso - y * degPerVerBlock;
float ullon = lso + x * degPerHorBlock;
float lrlat = ullat - degPerVerBlock;
float lrlon = ullon + degPerHorBlock;
// Get image data.
if (tsi != null) {
int index = y * numHorBlocks_N + x;
// Check for not blowing the list end...
if (index >= tsi.size()) {
return null;
}
// Subtracting one because the values look like they start
// with 1.
int blockOffset = ((DDFSubfield) tsi.get(index)).intValue() - 1;
if (Debug.debugging("asrp")) {
Debug.output("ASRPDirectory.getBlock: index of (" + x + ", "
+ y + ") is " + blockOffset);
}
if (blockOffset < 0) {
// Can't have a negative offset...
if (Debug.debugging("asrp")) {
Debug.output(" skipping...");
}
return null;
}
DDFModule mod = img.getInfo();
mod.seek(tileDataOffset + blockOffset);
int byteCount = 0; // Which data byte is being set
int numBlockPixels = numHorPixels_Q * numVerPixels_P;
byte[] data = new byte[numBlockPixels]; // image byte data
int rowCount = 0; // the per row count, should equal 128 (
// numHorPixels_Q) at the end of every
// row
int cpc = 0; // current pixel count for file pointer
int cpv = 0; // current pixel value for file pointer
while (byteCount < numBlockPixels) {
switch (pixelCountBits) {
case 8:
cpc = mod.read();
break;
case 4:
cpc = mod.read() >> 4;
// need to back pointer up 4 bits before reading
// cpv??
Debug.output("CAUTION: 4 bit count");
break;
default:
cpc = 1;
}
cpv = mod.read();
// OK, cpv has value, cpc says how many pixels that
// goes in.
try {
for (int c = 0; c < cpc; c++) {
rowCount++;
if (colors != null && cpv > colors.length) {
if (Debug.debugging("asrpdetail")) {
Debug.output("Got value that is too big for colortable");
}
}
data[byteCount + c] = (byte) cpv;
}
} catch (ArrayIndexOutOfBoundsException aioobe) {
if (Debug.debugging("asrp")) {
Debug.output("ASRPDirectory.getBlock(): bad index for setting byte value: "
+ aioobe.getMessage());
}
// This try/catch block is really for the data[]
// array indexing.
// if byteCount + x was greater than
// numBlockPixels,
// we should be at the end of the image bytes, so
// we
// shouldn't have to worry about rowCount not
// being
// properly updated.
}
byteCount += cpc;
if (rowCount == numHorPixels_Q) {
rowCount = 0;
}
}
if (Debug.debugging("asrpdetail")) {
Debug.output("ASRPDirectory creating image covering (" + ullat
+ ", " + ullon + "), (" + lrlat + ", " + lrlon + ")");
}
return new OMScalingRaster(ullat, ullon, lrlat, lrlon, numHorPixels_Q, numVerPixels_P, data, getColors(), 255);
}
return null;
}
/**
* Get the colors from the QAL file.
*/
protected Color[] getColors() {
if (colors == null) {
DDFField col = qal.getField(QualityFile.COLOUR_CODE_ID);
List reds = col.getSubfields("NSR");
List greens = col.getSubfields("NSG");
List blues = col.getSubfields("NSB");
int numColors = reds.size();
colors = new Color[numColors];
for (int count = 0; count < numColors; count++) {
int red = ((DDFSubfield) reds.get(count)).intValue();
int green = ((DDFSubfield) greens.get(count)).intValue();
int blue = ((DDFSubfield) blues.get(count)).intValue();
// Debug.output("Created color " + count + " with " +
// red + ", " + green + ", " + blue);
// The zero color is supposed to tbe null color, and
// clear. Doesn't seem to be working.
colors[count] = new Color(red, green, blue, (count == 0 ? 0
: 255));
}
}
return colors;
}
/**
* Read in the attribute information about the data.
*
* @param dirPath path to the ASRP directory.
* @param root name of all of the files, usually the same as the ASRP
* directory itself.
* @param DD the occurrence number, usually '01' of the files.
*/
protected void initialize(String dirPath, String root, String DD)
throws IOException {
String rootPath = dirPath + "/" + root + DD + ".";
gen = new GeneralInformationFile(rootPath + GEN_NAME);
ger = new GeoReferenceFile(rootPath + GER_NAME);
qal = new QualityFile(rootPath + QAL_NAME);
sou = new SourceFile(rootPath + SOURCE_NAME);
img = new RasterGeoDataFile(rootPath + IMAGE_NAME);
DDFField sprInfo = gen.getField(GeneralInformationFile.DATA_SET_PARAMETERS);
numHorBlocks_N = sprInfo.getSubfield("NFC").intValue();
numVerBlocks_M = sprInfo.getSubfield("NFL").intValue();
numHorPixels_Q = sprInfo.getSubfield("PNC").intValue();
numVerPixels_P = sprInfo.getSubfield("PNL").intValue();
pixelCountBits = sprInfo.getSubfield("PCB").intValue();
pixelValueBits = sprInfo.getSubfield("PVB").intValue();
// assume there is a tile index map
DDFField genInfo = gen.getField(GeneralInformationFile.GENERAL_INFORMATION);
swo = genInfo.getSubfield("SWO").floatValue() / 3600f;
neo = genInfo.getSubfield("NEO").floatValue() / 3600f;
nea = genInfo.getSubfield("NEA").floatValue() / 3600f;
swa = genInfo.getSubfield("SWA").floatValue() / 3600f;
lso = genInfo.getSubfield("LSO").floatValue() / 3600f;
pso = genInfo.getSubfield("PSO").floatValue() / 3600f;
arv = genInfo.getSubfield("ARV").intValue();
brv = genInfo.getSubfield("BRV").intValue();
DDFField timInfo = gen.getField(GeneralInformationFile.TILE_INDEX_MAP);
tsi = timInfo.getSubfields("TSI");
DDFField pixelInfo = img.getField(RasterGeoDataFile.PIXEL);
// Finding this out lets you use the tile index map to access
// pixel data. This offset points to the start of the tile
// data.
tileDataOffset = pixelInfo.getHeaderOffset()
+ pixelInfo.getDataPosition();
degPerHorBlock = 360f / (float) arv * (float) numHorPixels_Q;
degPerVerBlock = 360f / (float) brv * (float) numVerPixels_P;
if (Debug.debugging("asrp")) {
Debug.output("For " + rootPath + "\n\thave blocks ("
+ numHorBlocks_N + ", " + numVerBlocks_M
+ ")\n\twith pixels (" + numHorPixels_Q + ", "
+ numVerPixels_P + ")");
Debug.output("\tCoverage from (" + nea + ", " + swo + ") to ("
+ swa + ", " + neo + ")");
Debug.output("\tPadded coverage starting at (" + pso + ", " + lso
+ ")");
Debug.output("\tNumber of pixels 360 e-w (" + arv + ") , n-s ("
+ brv + ")");
Debug.output("\tdegrees per horizontal block: " + degPerHorBlock
+ ", vertical: " + degPerVerBlock);
Debug.output("\tImage Data made up of count bits: "
+ pixelCountBits + ", value bits: " + pixelValueBits);
if (Debug.debugging("asrpdetail")) {
Debug.output("Checking...");
float latdiff = nea - swa;
float londiff = neo - swo;
float horPixels = arv * (londiff / 360f);
float verPixels = brv * (latdiff / 360f);
Debug.output("\tCalculating " + (horPixels / numHorPixels_Q)
+ " hor blocks");
Debug.output("\tCalculating " + (verPixels / numVerPixels_P)
+ " hor blocks");
Debug.output("\tCalculating "
+ (lso + degPerHorBlock * numHorBlocks_N)
+ " end latitude");
Debug.output("\tCalculating "
+ (pso - degPerVerBlock * numVerBlocks_M)
+ " end latitude");
}
}
getColors();
gen.close();
ger.close();
qal.close();
sou.close();
img.close();
}
/**
* A private class to store cached images.
*/
private static class ASRPBlockCacheObject extends CacheObject {
/**
* @param id passed to superclass
* @param obj passed to superclass
*/
public ASRPBlockCacheObject(String id, OMGraphic obj) {
super(id, obj);
}
/**
* Calls dispose() on the contained frame, to make it eligible for
* garbage collection.
*/
//protected void finalize() {}
}
/**
* Load a block image into the cache, based on the relative coordinates of
* the block as a key.
*
* @param key String of form 'x,y' identifying the relative location of
* the subframe image.
* @return Block image, hidden as a CacheObject.
*/
public CacheObject load(Object key) {
if (key != null) {
String xAndY = key.toString();
int commaIndex = xAndY.indexOf(',');
int x = Integer.parseInt(xAndY.substring(0, commaIndex));
int y = Integer.parseInt(xAndY.substring(commaIndex + 1));
if (Debug.debugging("asrpdetail")) {
Debug.output("Getting tiled image " + x + ", " + y + " (from "
+ xAndY + ")");
}
try {
OMGraphic block = getBlock(x, y);
if (block != null) {
return new ASRPBlockCacheObject(xAndY.intern(), block);
}
} catch (IOException ioe) {
Debug.error("ASRPDirectory caught exception creating tiled image for "
+ xAndY);
}
}
return null;
}
public static void main(String[] argv) {
Debug.init();
if (argv.length < 1) {
Debug.output("Usage: ASRPDirectory dir_pathname");
} else {
new ASRPDirectory(argv[0]);
}
System.exit(0);
}
}