// **********************************************************************
//
// <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/layer/rpf/RpfTocHandler.java,v $
// $RCSfile: RpfTocHandler.java,v $
// $Revision: 1.16 $
// $Date: 2006/12/13 16:45:24 $
// $Author: dietrick $
//
// **********************************************************************
/**
* Modifications: 1. Changed getBestCoverageEntry() to consider more than one zone. 2. Changed getBestCoverageEntry() to
* return multiple entries.
*/
/*
* The meat of this code is based on source code provided by The MITRE
* Corporation, through the browse application source code. Many
* thanks to Nancy Markuson who provided BBN with the software, and to
* Theron Tock, who wrote the software, and Daniel Scholten, who
* revised it - (c) 1994 The MITRE Corporation for those parts, and
* used/distributed with permission. The RPF TOC reading mechanism is
* the contributed part.
*/
package com.bbn.openmap.layer.rpf;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import com.bbn.openmap.io.BinaryBufferedFile;
import com.bbn.openmap.io.BinaryFile;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.proj.CADRG;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
/**
* The RpfTocHandler knows how to read A.TOC files for RPF raster data. The A.TOC file describes the coverage found in
* the tree of data that accompanies it. This coverage is described as a series of rectangles describing the frame of
* groups of coverage, with common-scale maps, types for different CADRG zones. The RpfTocHandler can also provide a
* description of the frames and subframes to use for a screen with a given projection.
* <P>
*
* The RPF specification says that the frame paths and file names, from the RPF directory, should be in upper-case
* letters. The paths and file names are stored in the A.TOC file this way. Sometimes, however, through CDROM and
* downloading quirks, the paths and file names, as stored on the hard drive, are actually transferred to lower-case
* letters. This RpfTocHandler will check for lower case letter paths, but only for all the letters to be lower case.
* The frame will be marked as non-existent if some of the directories or filenames have be transformed to uppercase.
*/
public class RpfTocHandler {
public final static String RPF_TOC_FILE_NAME = "A.TOC";
public final static String LITTLE_RPF_TOC_FILE_NAME = "a.toc";
public final static int DEFAULT_FRAME_SPACE = 300; // frame file
// in kilobytes
protected RpfHeader head;
protected String aTocFilePath;
protected boolean aTocByteOrder;
protected BinaryFile binFile;
protected RpfFileSections.RpfLocationRecord[] locations;
/**
* The boundary rectangles in the A.TOC file.
*/
protected RpfTocEntry[] entries;
protected String dir;
protected boolean Dchum;
protected long estimateDiskSpace; // uint
protected int numBoundaries;
protected long numFrameIndexRecords; // uint # frame file index records
protected int indexRecordLength; // ushort, frame file index record
// length
protected long currencyTime;
protected boolean valid = false;
/**
* Set by the RpfFrameProvider, and used to track down this particular TOC to get to the frames offered by it's
* coverages.
*/
private int tocNumber = 0;
/**
* Flag to tell the TOC handler to not consider matching zones when evaluating coverage boxes for which one provides
* the best coverage for a projection. Because the subframes are being scaled and warped, it's better to get
* coverage from any zone rather than to limit the entry responses with zone matching the projection.
*/
private boolean ignoreZonesForCoverageBoxes = true;
/**
* Flag to note whether absolute pathnames are used in the A.TOC. Set to false, because it's not supposed to be that
* way, according to the specification. This is reset automatically when the A.TOC file is read. If the first two
* characters of the directory paths are ./, then it stays false.
*/
protected boolean fullPathsInATOC = false;
protected boolean DEBUG_RPF = false;
protected boolean DEBUG_RPFTOC = false;
protected boolean DEBUG_RPFTOCDETAIL = false;
protected boolean DEBUG_RPFTOCFRAMEDETAIL = false;
// Added zone extents
private static final int CADRG_zone_extents[] = {
0,
32,
48,
56,
64,
68,
72,
76,
80,
90
};
public RpfTocHandler() {
DEBUG_RPF = Debug.debugging("rpf");
DEBUG_RPFTOC = Debug.debugging("rpftoc");
DEBUG_RPFTOCDETAIL = Debug.debugging("rpftocdetail");
DEBUG_RPFTOCFRAMEDETAIL = Debug.debugging("rpftocframedetail");
estimateDiskSpace = DEFAULT_FRAME_SPACE;
if (Debug.debugging("rpftoc")) {
Debug.error("RpfTocHandler: No TOC parent directory name in constructor");
}
}
/**
* Should be used in situations where it is certain that this is the only A.TOC in town.
*/
public RpfTocHandler(String parentDir) {
this(parentDir, 0);
}
/**
* Used when there is more than one A.TOC being used, or where there is a possibility of that happening, like in the
* RPF layer. The TOC number should be unique for a certain RpfFrameProvider.
*
* @param parentDir the RPF directory
* @param TOCNumber a unique number to identify this TOC for a RpfFrameProvider.
*/
public RpfTocHandler(String parentDir, int TOCNumber) {
tocNumber = TOCNumber;
estimateDiskSpace = DEFAULT_FRAME_SPACE;
/* DKS. Open input "A.TOC" */
valid = loadFile(parentDir);
if (!valid) {
Debug.error("RpfTocHandler: Invalid TOC File in " + parentDir);
}
}
/**
* Given a parent RPF directory, find the a.toc file directly inside it, as dictated by the specification. Not
* called anymore - the BinaryFile does the searching, and can find URL and jar files.
*
* @param parentDir Path to the RPF directory.
* @return File
*/
public File getTocFile(String parentDir) {
/* DKS. Open input "A.TOC" */
File file = new File(parentDir + "/" + RPF_TOC_FILE_NAME);
if (!file.exists()) {
file = new File(parentDir + "/" + LITTLE_RPF_TOC_FILE_NAME);
if (!file.exists()) {
// Debug.error("RpfTocHandler: getTocFile(): file in
// "+
// parentDir + " not found");
return null;
}
}
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: getTocFile(): TOC file is " + file);
}
return file;
}
/**
* True if the A.TOC file is readable/present/good.
*/
public boolean isValid() {
return valid;
}
/**
* A way to check if the status of the A.TOC file is different, in case another one has taken its place. Handy if
* the A.TOC is on a CDROM drive and the disk has been swapped. Not valid anymore, with the advent of the new
* BinaryFile, where the file information may not be available.
*/
public boolean hasChanged() {
// File tmpFile = getTocFile(dir);
// if (tmpFile == null) {
// return valid;
// }
// if (tmpFile.lastModified() != currencyTime && valid) {
// valid = false;
// return true;
// }
return false;
}
/**
* Re-read the A.TOC file in the parent directory.
*/
public boolean reload() {
return loadFile(dir);
}
/**
* Read the file and load its parameters into this object.
*/
public boolean loadFile(String parentDir) {
boolean ret = true;
String upperCaseVersion = parentDir + "/" + RPF_TOC_FILE_NAME;
String lowerCaseVersion = parentDir + "/" + LITTLE_RPF_TOC_FILE_NAME;
try {
if (BinaryFile.exists(upperCaseVersion)) {
binFile = new BinaryBufferedFile(upperCaseVersion);
aTocFilePath = upperCaseVersion;
} else if (BinaryFile.exists(lowerCaseVersion)) {
binFile = new BinaryBufferedFile(lowerCaseVersion);
aTocFilePath = lowerCaseVersion;
}
if (binFile == null) {
return false;
}
if (DEBUG_RPFTOC) {
Debug.output("RpfTocHandler: TOC file is in " + parentDir);
}
dir = parentDir + "/";
// With the new BinaryFile, we can't get to this
// info, because we aren't using File objects anymore.
// currencyTime = file.lastModified();
if (!parseToc(binFile)) {
ret = false;
Debug.error("RpfTocHandler: loadFile(): error parsing A.TOC file!!");
}
aTocByteOrder = binFile.byteOrder();
binFile.close();
} catch (IOException e) {
ret = false;
}
binFile = null;
return ret;
}
protected boolean parseToc(BinaryFile binFile) {
if (DEBUG_RPFTOC) {
Debug.output("ENTER TOC parsing...");
}
try {
// binFile should be set to the beginning at this point
binFile.seek(0);
// Read header
head = new RpfHeader();
if (!head.read(binFile)) {
return false;
}
if (DEBUG_RPFTOC) {
Debug.output("RpfTocHandler.parseToc: read header:\n" + head);
}
binFile.seek(head.locationSectionLocation);
RpfFileSections rfs = new RpfFileSections(binFile);
// Everything must be OK to reach here...
// DKS. fseek to start of location section: 48
// DFD not necessarily 48! New A.TOCs are different.
locations = rfs.getLocations(RpfFileSections.TOC_LOCATION_KEY);
// Read boundary rectangles
// Number of Boundary records
// DKS: now phys_index, not index
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): fseek to Boundary section subheader: "
+ locations[0].componentLocation);
}
binFile.seek(locations[0].componentLocation);
// NEW
long boundRectTableOffset = (long) binFile.readInteger();
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): BoundRectTableOffset: " + boundRectTableOffset);
}
int n = (int) binFile.readShort();
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): # Boundary rect. recs: " + n);
}
numBoundaries = n;
// DKS new
// Boundary record length
int boundaryRecordLength = (int) binFile.readShort();
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): should be 132: " + boundaryRecordLength);
}
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): fseek to Boundary Rectangle Table: "
+ locations[1].componentLocation);
}
binFile.seek(locations[1].componentLocation);
entries = new RpfTocEntry[numBoundaries];
// Read Boundary rectangle records
for (int i = 0; i < n; i++) {
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): read boundary rec#: " + i);
}
// All this stuff moved to RpfTocEntry.java - DFD
// 8/18/99
entries[i] = new RpfTocEntry(binFile, tocNumber, i);
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): entry " + i + " has scale " + entries[i].scale + ", type "
+ (entries[i].Cib ? "CIB" : "CADRG") + " in zone " + entries[i].zone);
if (entries[i].Cib) {
Debug.output("RpfTocHandler: parseToc(): entry noted as a Cib entry.");
}
}
}
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): Read frame file index section subheader at loc: "
+ locations[2].componentLocation);
}
// Read # of frame file index records
// Skip 1 byte security classification
// locations[2] is loc of frame file index section
// subheader
binFile.seek(locations[2].componentLocation + 1);
// NEW
long frameIndexTableOffset = (long) binFile.readInteger();
numFrameIndexRecords = (long) binFile.readInteger();
int numPathnameRecords = (int) binFile.readShort();
// indexRecordLength should now be 33, not 35
indexRecordLength = (int) binFile.readShort();
if (DEBUG_RPFTOCDETAIL) {
Debug.output("RpfTocHandler: parseToc(): frameIndexTableOffset: " + frameIndexTableOffset);
Debug.output("RpfTocHandler: parseToc(): # Frame file index recs: " + numFrameIndexRecords);
Debug.output("RpfTocHandler: parseToc(): # pathname records: " + numPathnameRecords);
Debug.output("RpfTocHandler: parseToc(): Index rec len(33): " + indexRecordLength);
}
// In previous version of the RpfTocHandler, we went ahead and read
// in all of the RPF frame file paths. For very large collections of
// data, expecially with large scale charts and imagery, this turns
// out to use a huge amount of data. The code has been reorganized
// to skip this part until it's been determined that those files are
// actually going to be used by the layer.
// readFrameInformation(binFile);
// One thing that we still need to do at this point, is query some
// of the frame file paths to find out what chart type is being held
// for each RpfTocEntry, so during the coverage determination we can
// decide whether to use an RpfTocEntry or not.
figureOutChartSeriesForEntries(binFile);
} catch (IOException ioe) {
Debug.error("RpfTocHandler: IO ERROR parsing file!\n\t" + ioe);
return false;
} catch (FormatException fe) {
Debug.error("RpfTocHandler: Format ERROR parsing file!\n\t" + fe);
return false;
}
if (DEBUG_RPFTOC) {
Debug.output("LEAVE TOC parsing...");
}
return true;
}
/**
* Method that looks at one frame file for each RpfTocEntry, in order to check the suffix and load the chart series
* information into the RpfTocEntry. This is needed for when the RpfTocHandler is asked for matching RpfTocEntries
* for a given scale and location when the chart type has been limited to a certain chart code.
*
* @param binFile
* @throws IOException
* @throws FormatException
*/
protected void figureOutChartSeriesForEntries(BinaryFile binFile)
throws IOException, FormatException {
RpfTocEntry[] entriesAlreadyChecked = new RpfTocEntry[entries.length];
System.arraycopy(entries, 0, entriesAlreadyChecked, 0, entries.length);
// We just need the name of one file, just to see what the series code
// is.
for (int i = 0; i < numFrameIndexRecords; i++) {
// Read frame file index records
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("RpfTocHandler: parseToc(): Read frame file index rec #: " + i);
}
// Index_subhdr_len (9) instead of table_offset (11)
// indexRecordLength (33) instead of 35
// componentLocation, not index
// locations[3] is frame file index table subsection
binFile.seek(locations[3].componentLocation + indexRecordLength * i);
int boundaryId = (int) binFile.readShort();
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("boundary id for frame: " + i + " is " + boundaryId);
}
if (boundaryId > numBoundaries - 1) {
throw new FormatException("Bad boundary id in FF index record " + i);
}
RpfTocEntry entry = entriesAlreadyChecked[boundaryId];
if (entry == null) {
continue; // already checked.
} else {
entriesAlreadyChecked[boundaryId] = null;
}
/* int frameRow = (int) */ binFile.readShort();
/* int frameCol = (int) */
binFile.readShort();
/* long pathOffset = (long) */
binFile.readInteger();
String filename = binFile.readFixedLengthString(12);
// Figure out the chart series ID
int dot = filename.lastIndexOf('.');
// Interned so we can look it up in the catalog
// later...
entry.setInfo(filename.substring(dot + 1, dot + 3).intern());
} /* for i = numFrameIndexRecords */
}
/**
* Should be called by the RpfFrameCacheHandler before any frame files are loaded from a RpfTocEntry. The
* RpfFrameCacheHandler should ask the RpfTocEntry if the frames have been loaded, and call this if they have not.
*/
protected void loadFrameInformation(RpfTocEntry rpfTocEntry) {
try {
if (binFile == null && aTocFilePath != null) {
binFile = new BinaryBufferedFile(aTocFilePath);
binFile.byteOrder(aTocByteOrder);
readFrameInformation(binFile, rpfTocEntry);
binFile.close();
binFile = null;
}
} catch (IOException ioe) {
Debug.error("RpfTocHandler: IO ERROR parsing file for frame information!\n\t" + ioe);
} catch (FormatException fe) {
Debug.error("RpfTocHandler: Format ERROR parsing file for frame information!\n\t" + fe);
}
}
/**
* Reads the BinaryFile to retrieve the Frame file information for the entry.
*
* @param binFile a valid, open BinaryFile.
* @param entry the RpfTocEntry to fill.
* @throws IOException
* @throws FormatException
*/
protected void readFrameInformation(BinaryFile binFile, RpfTocEntry entry)
throws IOException, FormatException {
int boundaryId, frameRow, frameCol; // ushort
int currentPosition;
int pathLength;
long pathOffset; // uint, offset of frame file pathname
RpfFrameEntry frame;
int currentBoundaryIdForEntry = entry.coverage.entryNumber;
// Read frame file index records
for (int i = 0; i < numFrameIndexRecords; i++) {
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("RpfTocHandler: parseToc(): Read frame file index rec #: " + i);
}
// Index_subhdr_len (9) instead of table_offset (11)
// indexRecordLength (33) instead of 35
// componentLocation, not index
// locations[3] is frame file index table subsection
binFile.seek(locations[3].componentLocation + indexRecordLength * i);
boundaryId = (int) binFile.readShort();
if (boundaryId != currentBoundaryIdForEntry) {
// Only load the frame names of the entry we are using...
continue;
}
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("boundary id for frame: " + i + " is " + boundaryId);
}
// DKS NEW: changed from 1 to 0 to agree w/ spec. -1
// added also.
// if (boundaryId < 0 || boundaryId > numBoundaries -
// 1 )
if (boundaryId > numBoundaries - 1) {
throw new FormatException("Bad boundary id in FF index record " + i);
}
frameRow = (int) binFile.readShort();
frameCol = (int) binFile.readShort();
// DKS. switched from horizFrames to vertFrames
// DKS NEW: CHANGED FROM 1 to 0 to agree w/spec. ALSO
// COL below
// if (frameRow < 1 || frameRow > entry->vertFrames)
if (frameRow > entry.vertFrames - 1) {
throw new FormatException("Bad row number: " + frameRow + ", in FF index record " + i
+ ", Min row num=0; Max. row num:" + (entry.horizFrames - 1));
}
// DKS. switched from vertFrames to horizFrames
if (frameCol > entry.horizFrames - 1) {
throw new FormatException(" Bad col number in FF index record " + i);
}
// DKS NEW: -1 removed on frameRow, col
// JRB
// frame = &entry->frames[frameRow][frameCol];
// [(entry->vertFrames - 1L)-frameRow] flips the array
// over, so that the frames can be referenced
// correctly from the top left, instead of the
// specification notation of bottom left.
frame = entry.getFrame((entry.vertFrames - 1) - frameRow, frameCol);
if (frame.exists && DEBUG_RPFTOCDETAIL) {
Debug.output("FF " + i + " is a duplicate");
}
// DKS: phys_loc deleted
// pathname offset
pathOffset = (long) binFile.readInteger();
if (pathOffset < 0) {
continue;
}
// Save file position for later
currentPosition = (int) binFile.getFilePointer();
// Go to start of pathname record
// DKS. New pathOffset offset from start of frame file
// index section of TOC??
// DKS. Add pathoffset wrt frame file index table
// subsection (loc[3])
binFile.seek(locations[3].componentLocation + pathOffset);
pathLength = (int) binFile.readShort();
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("RpfTocHandler: parseToc(): pathLength:" + pathLength);
}
// 1st part of directory name is passed as arg:
// e.g. "../RPF2/"
String rpfdir = dir;
StringBuffer sBuf = new StringBuffer(pathLength);
// read rest of directory name from toc
// DKS: skip 1st 2 chars: "./":
String pathTest = binFile.readFixedLengthString(2);
if (pathTest.equals("./")) {
fullPathsInATOC = false;
} else {
fullPathsInATOC = true;
}
if (!fullPathsInATOC) {
// DKS: Make up for skipped 2 chars
sBuf.append(binFile.readFixedLengthString(pathLength - 2));
} else {
sBuf.append(pathTest);
sBuf.append(binFile.readFixedLengthString(pathLength - 2));
}
// Add the trim because it looks like NIMA doesn't
// always get the pathLength correct...
String directory = sBuf.toString().trim();
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("RpfTocHandler: parseToc(): frame directory: " + directory);
}
/* Go back to get filename tail */
binFile.seek(currentPosition);
String filename = binFile.readFixedLengthString(12);
if (DEBUG_RPFTOCFRAMEDETAIL) {
Debug.output("RpfTocHandler: parseToc(): frame filename: " + filename);
}
// Figure out the chart series ID
int dot = filename.lastIndexOf('.');
// Interned so we can look it up in the catalog
// later...
entry.setInfo(filename.substring(dot + 1, dot + 3).intern());
// We duplicate this below!!!
// frame.framePath = new String(frame.rpfdir +
// frame.directory +
// "/" + frame.filename);
// DKS new DCHUM. Fill in last digit v of vv version
// #. fffffvvp.JNz or ffffffvp.IMz for CIB boundaryId
// will equal frame file number: 1 boundary rect. per
// frame.
// if (Dchum)
// entries[boundaryId].version =
// frame.filename.charAt(6);
String tempPath;
if (!fullPathsInATOC) {
tempPath = rpfdir + directory + filename;
frame.rpfdirIndex = (short) (rpfdir.length() - 3);
frame.filenameIndex = (short) (rpfdir.length() + directory.length());
} else {
tempPath = directory + filename;
frame.filenameIndex = (short) directory.length();
}
frame.framePath = tempPath;
frame.exists = true;
// You don't want to check for the existance of frames here, do
// it at load time, and then mark the entry if the load fails.
// If you do the check here, you waste a lot of I/O time and
// effort. Assume it's there, the RPFFrame has been modified
// to try lower case names if needed.
if (frame.framePath == null) {
Debug.output("RpfTocHandler: Frame " + tempPath
+ " doesn't exist. Please rebuild A.TOC file using MakeToc, or check read permissions for the file.");
}
} /* for i = numFrameIndexRecords */
}
/**
* Util-like function that translates a long to the string representation found in the A.TOC file.
*/
public static String translateScaleToSeries(long scale) {
if (scale == 0) {
return "Various ";
} else if (scale == 50000L) {
return "1:50K ";
} else if (scale == 100000L) {
return "1:100K ";
} else if (scale == 200000L) {
return "1:200K ";
} else if (scale == 250000L) {
return "1:250K ";
} else if (scale == 500000L) {
return "1:500K ";
} else if (scale == 1000000L) {
return "1:1M ";
} else if (scale == 2000000L) {
return "1:2M ";
} else if (scale == 5000000L) {
return "1:5M ";
} else if (scale == 66666L) {
return "10M ";
} else if (scale == 33333L) {
return "5M ";
} else {
return (String) null;
}
}
/**
* Given the scale string found in the A.TOC file, decode it into a 'long' scale.
*/
public static long textScaleToLong(String textScale) {
long resolution = 1l;
long realValue;
int expLetter; // location of m, M, K
int expLetterSmall;
// Make sure there are no commas, commas seem to kill Long parsing.
int commaIndex = textScale.indexOf(',');
while (commaIndex != -1) {
StringBuffer buf = new StringBuffer(textScale.substring(0, commaIndex));
buf.append(textScale.substring(commaIndex + 1));
textScale = buf.toString();
commaIndex = textScale.indexOf(',');
}
int colon = textScale.indexOf(":");
try {
if (colon == -1) {
// dealing with an imagery scale
expLetter = textScale.indexOf("m");
if (expLetter == -1) {
expLetter = textScale.indexOf("M");
}
if (expLetter != -1) {
resolution = Long.parseLong(textScale.substring(0, expLetter));
return (long) (resolution / .000150);
}
// If we get here, we're dealing with a chart scale that doesn't
// have a 1: at the front of it, so continue on...
}
// dealing with a map scale
String expValue = "";
expLetter = textScale.lastIndexOf('K');
expLetterSmall = textScale.lastIndexOf('k');
if (expLetter == -1 && expLetterSmall == -1) {
expLetter = textScale.lastIndexOf('M');
expLetterSmall = textScale.lastIndexOf('m');
if (expLetter != -1 || expLetterSmall != -1) {
expValue = "000000";
}
} else {
expValue = "000";
}
StringBuffer buf;
if (expValue.length() > 0) {
// make sure we have the right index variable
if (expLetter == -1) {
expLetter = expLetterSmall;
}
// If there isn't a colon, this should be OK
buf = new StringBuffer(textScale.substring(colon + 1, expLetter));
buf.append(expValue);
} else {
buf = new StringBuffer(textScale.substring(colon + 1));
}
String longString = buf.toString().trim();
realValue = Long.parseLong(longString);
} catch (NumberFormatException nfe) {
if (Debug.debugging("rpftoc")) {
Debug.output("textScaleToLong: Number Format Exception!!!!" + textScale);
}
return (long) RpfConstants.Various;
} catch (StringIndexOutOfBoundsException sioobe) {
if (Debug.debugging("rpftoc")) {
Debug.output("textScaleToLong: String index out of bounds:\n" + sioobe.getMessage());
}
return (long) RpfConstants.Various;
}
if (colon != -1) {
resolution = Long.parseLong(textScale.substring(0, colon));
}
long ret = (realValue / resolution);
if (Debug.debugging("rpftoc")) {
Debug.output("RpfTocHandler: textScaleToLong converted " + textScale + " to " + ret);
}
return ret;
}
protected int getASCIIZone(double ullat, int zone) {
int z = zone;
// Now convert it to ASCII to compare
if (ullat > 0) {
z += 48; // for ASCII compare next
} else {
z += 64;
if (z == 73) {
z++; // Can't be equal to I -> J
}
}
return z;
}
/**
* Given a coordinate box and a scale, return the entries that have coverage over the given area. The chart types
* returned are dictated by the chartSeriesCode passed in, which must be an entry from an RpfProductInfo.seriesCode.
*
* @param ullat upper left latitude, in decimal degrees
* @param ullon upper left longitude, in decimal degrees
* @param lrlat lower right latitude, in decimal degrees
* @param lrlon lower right longitude, in decimal degrees
* @param proj CADRG projection describing map.
* @param chartSeriesCode chart selection. If null, all coverage boxes fitting on the screen will be returned.
* @param coverages a list of potential coverages
*/
public void getCatalogCoverage(double ullat, double ullon, double lrlat, double lrlon, Projection proj,
String chartSeriesCode,
Vector<RpfCoverageBox> coverages) {
if (!valid) {
return;
}
String chartSeries;
for (int i = 0; i < numBoundaries; i++) {
// Try to get the boundary rectangle with the most
// coverage, so reset the entry for this particular query.
entries[i].coverage.reset();
if (chartSeriesCode == null) {
chartSeries = RpfViewAttributes.ANY;
} else {
chartSeries = chartSeriesCode;
}
if (chartSeries.equalsIgnoreCase(RpfViewAttributes.ANY) || chartSeries.equalsIgnoreCase(
entries[i].info.seriesCode)) {
if (entries[i].coverage.setPercentCoverage(ullat, ullon, lrlat, lrlon) > 0f) {
coverages.addElement(entries[i].coverage);
}
}
}
}
/**
* Given a coordinate box and a scale, find the entry in the table of contents file with the right data. Zone is
* always of the northern hemisphere, and is transformed to southern inside if needed. The box will get filled in
* with the correct information. The subframe description will have scaling information for the subframes to be
* scaled to match the scale. If proj is null, only exact matches will be found
*
* NOTE: method getZone() of the CADRG projection is only relevant (according to OpenMap documentation) when you're
* viewing a map type (ONC, etc) at its proper scale (i.e. 1:1mil for ONC). There was a method in RpfTocHandler that
* only checked a TOC for coverage if the TOC zone matched the zone of the projection. This caused gaps of coverage
* when viewing the maps at large scales that were different from their proper scale (e.g. viewing JNC at 1:10mil).
* Modified this method so that it obtains all the possible zones the current map projection could be in, and
* compares the TOC zones to that set.
*
* Note that this now returns a list of coverage entries instead of just one.
*
* @param ullat upper left latitude, in decimal degrees
* @param ullon upper left longitude, in decimal degrees
* @param lrlat lower right latitude, in decimal degrees
* @param lrlon lower right longitude, in decimal degrees
* @param proj CADRG projection describing map.
* @param viewAtts view attributes determine chart selection.
* @return a Vector of applicable RpfCoverageBoxes.
*/
public List<RpfTocEntry> getBestCoverageEntry(double ullat, double ullon, double lrlat, double lrlon, Projection proj,
RpfViewAttributes viewAtts) {
if (!valid) {
return null;
}
List<RpfTocEntry> coverageEntries = new Vector<RpfTocEntry>();
double scaleFactor = 0;
double lowerScaleFactorLimit = 1.0;
double upperScaleFactorLimit = 1.0;
// Good for a preliminary check. It has to start at least as
// 4 to have one corner matching.
int prevBoundaryHits = 0;
if (viewAtts != null) {
lowerScaleFactorLimit = (double) (1.0 / viewAtts.imageScaleFactor);
upperScaleFactorLimit = (double) viewAtts.imageScaleFactor;
}
int nscale = 0;
int scale = (int) proj.getScale();
RpfTocEntry bestEntry = null;
if (DEBUG_RPFTOCDETAIL) {
Debug.output("getBestCoverageEntry(): Checking for coverage");
Debug.output(" nw_lat: " + ullat);
Debug.output(" se_lat: " + lrlat);
Debug.output(" nw_lon: " + ullon);
Debug.output(" se_lon: " + lrlon);
}
CADRG cadrg = CADRG.convertProjection(proj);
int zone = getASCIIZone(ullat, cadrg.getZone());
char okZones[] = getOkZones(ullat, lrlat, (char) zone);
for (RpfTocEntry currentEntry : entries) {
if (DEBUG_RPFTOCDETAIL) {
Debug.output("********************");
Debug.output(" tochandler: Boundary #" + currentEntry.coverage.entryNumber);
Debug.output(currentEntry.toString());
}
// Try to get the boundary rectangle with the most
// coverage, so reset the entry for this particular query.
currentEntry.coverage.reset();
// Find the scale of the boundary rectangle
if (currentEntry.info == null || currentEntry.info.scale == RpfConstants.Various) {
nscale = (int) textScaleToLong(currentEntry.scale);
// Reset the RpfProductInfo to the listed parameters
// in the A.TOC file.
currentEntry.altScale = (float) nscale;
currentEntry.altScaleString = currentEntry.scale;
currentEntry.coverage.scale = (float) nscale;
} else {
currentEntry.coverage.scale = currentEntry.info.scale;
nscale = (int) currentEntry.info.scale;
}
if (DEBUG_RPFTOCDETAIL) {
Debug.output("getBestCoverageEntry(): Query scale = " + scale + " vs. brect scale = " + nscale);
}
// if you want an exact match for scale...
if (viewAtts != null && !viewAtts.scaleImages) {
if (scale == nscale) {
scaleFactor = 1.0;
} else {
scaleFactor = lowerScaleFactorLimit - 1.0;
}
} else {
scaleFactor = (double) nscale / (double) scale;
}
String chartSeries;
if (viewAtts == null) {
chartSeries = RpfViewAttributes.ANY;
} else {
chartSeries = viewAtts.chartSeries;
}
if (scaleFactor >= lowerScaleFactorLimit
&& scaleFactor <= upperScaleFactorLimit
&& (chartSeries.equalsIgnoreCase(RpfViewAttributes.ANY) || chartSeries.equalsIgnoreCase(
currentEntry.info.seriesCode))) {
if (ignoreZonesForCoverageBoxes || isOkZone(currentEntry.zone, okZones)) {
// sets currentEntry.coverage.boundaryHits
int hits = currentEntry.coverage.setBoundaryHits(ullat, ullon, lrlat, lrlon);
if (DEBUG_RPFTOCDETAIL) {
Debug.output("getBestCoverageEntry(): Boundary Hits = " + hits);
}
if (bestEntry != null) {
boolean betterScale = false;
float newScaleDiff = RpfFrameCacheHandler.scaleDifference(proj, currentEntry.coverage);
float bestScaleDiff = RpfFrameCacheHandler.scaleDifference(proj, bestEntry.coverage);
float currentEntryPercentCoverage = currentEntry.coverage
.setPercentCoverage(ullat, ullon, lrlat, lrlon);
if ((currentEntryPercentCoverage == 100f
|| (viewAtts != null && viewAtts.scaleMoreImportantThanCoverage))
&& newScaleDiff <= bestScaleDiff) {
betterScale = true;
}
if (betterScale
&& (currentEntryPercentCoverage >= bestEntry.coverage.getPercentCoverage())
&& (hits >= prevBoundaryHits || hits >= 6)) {
// Add to list if has any hits and is
// the best possible scale. If new scale
// difference
// is strictly better, remove other
// entries
if (newScaleDiff < bestScaleDiff) {
coverageEntries.clear();
}
coverageEntries.add(currentEntry);
bestEntry = currentEntry;
prevBoundaryHits = hits;
if (DEBUG_RPFTOC) {
Debug.output("getBestCoverageEntry(): Found a match in a BR with coverage of "
+ currentEntry.coverage.getPercentCoverage() + "%.");
}
} else if (betterScale && currentEntry.coverage.getPercentCoverage() > 0f) {
if (newScaleDiff < bestScaleDiff) {
coverageEntries.clear();
}
coverageEntries.add(currentEntry);
bestEntry = currentEntry;
prevBoundaryHits = hits;
}
} else if (hits > prevBoundaryHits
&& (currentEntry.coverage.setPercentCoverage(ullat, ullon, lrlat, lrlon) > 0f)) {
bestEntry = currentEntry;
prevBoundaryHits = hits;
// Add to list of coverageEntries
coverageEntries.add(currentEntry);
if (DEBUG_RPFTOC) {
Debug.output("getBestCoverageEntry(): Found a match in a BR with coverage of "
+ currentEntry.coverage.getPercentCoverage() + "%.");
}
}
}
}
}
if (DEBUG_RPFTOC) {
if (bestEntry != null) {
Debug.output("getBestCoverageEntry(): found the best");
Debug.output("################");
Debug.output(bestEntry.toString());
Debug.output("Returning the following coverage boxes: ");
for (int i = 0; i < coverageEntries.size(); i++) {
Debug.output(coverageEntries.get(i).toString());
}
} else {
Debug.output("getBestCoverageEntry(): no box found");
}
}
return coverageEntries;
}
public static char[] getOkZones(double ullat, double lrlat, char zone) {
// allow a maximum of 3 additional zones in either direction
char[] okZones = new char[7];
// add zone from projection
okZones[0] = zone;
// check above
char currentZone = zone;
char backupZone;
int i = 0;
for (; i < 3; i++) {
if (isAboveZone(ullat, currentZone)) {
backupZone = getHigherZone(currentZone);
okZones[i + 1] = backupZone;
currentZone = backupZone;
} else {
break;
}
}
// check below
int k = i;
currentZone = zone;
for (; k < i + 3; k++) {
if (isBelowZone(ullat, currentZone)) {
backupZone = getLowerZone(currentZone);
okZones[k + 1] = backupZone;
currentZone = backupZone;
} else {
break;
}
}
int size = 0;
for (int j = 0; j < okZones.length; j++) {
if (okZones[j] != 0) {
size++;
}
}
char[] returnZones = new char[size];
System.arraycopy(okZones, 0, returnZones, 0, size);
return returnZones;
}
public static boolean isOkZone(char zone, char[] okZones) {
boolean ok = false;
for (int i = 0; i < okZones.length; i++) {
if (zone == okZones[i]) {
ok = true;
}
}
return ok;
}
protected static boolean isBelowZone(double lowerLat, char zone) {
float zoneLowerLat = getLowerZoneExtent(zone);
if (lowerLat < zoneLowerLat) {
return true;
} else {
return false;
}
}
protected static boolean isAboveZone(double upperLat, char zone) {
float zoneUpperLat = getUpperZoneExtent(zone);
if (upperLat > zoneUpperLat) {
return true;
} else {
return false;
}
}
public static float getUpperZoneExtent(char zone) {
if (zone >= '0' && zone <= '9') {
int i = zone - 49;
return CADRG_zone_extents[i + 1];
} else {
int i = zone - 65;
if (i == 9) {
i--; // Special care for j
}
return -1 * CADRG_zone_extents[i];
}
}
public static float getLowerZoneExtent(char zone) {
if (zone >= '0' && zone <= '9') {
int i = zone - 49;
return CADRG_zone_extents[i];
} else {
int i = zone - 65;
if (i == 9) {
i--; // Special care for J
}
return -1 * CADRG_zone_extents[i + 1];
}
}
public static char getLowerZone(char zone) {
// if zone = 'J' do nothing
if (zone >= '2' && zone <= '9') {
zone--;
} else if (zone == '1') {
zone = 'A';
} else if (zone >= 'A' && zone < 'H') {
zone++;
} else if (zone == 'H') {
zone = 'J';
}
return zone;
}
public static char getHigherZone(char zone) {
// if zone = '9' do nothing
if (zone >= '1' && zone < '9') {
zone++;
} else if (zone >= 'B' && zone < 'J' || zone >= 'b' && zone < 'j') {
zone--;
} else if (zone == 'J' || zone == 'j') {
zone -= 2;
} else if (zone == 'A' || zone == 'a') {
zone = '1';
}
return zone;
}
/**
* Return the list of grouped frames.
*/
public RpfTocEntry[] getEntries() {
return entries;
}
public String getATocFilePath() {
return aTocFilePath;
}
public void setATocFilePath(String tocFilePath) {
aTocFilePath = tocFilePath;
}
public boolean isFullPathsInATOC() {
return fullPathsInATOC;
}
public void setFullPathsInATOC(boolean fullPathsInATOC) {
this.fullPathsInATOC = fullPathsInATOC;
}
/**
* @return the ignoreZonesForCoverageBoxes
*/
public boolean isIgnoreZonesForCoverageBoxes() {
return ignoreZonesForCoverageBoxes;
}
/**
* @param ignoreZonesForCoverageBoxes the ignoreZonesForCoverageBoxes to set
*/
public void setIgnoreZonesForCoverageBoxes(boolean ignoreZonesForCoverageBoxes) {
this.ignoreZonesForCoverageBoxes = ignoreZonesForCoverageBoxes;
}
public static void main(String[] args) {
if (args.length != 1) {
Debug.output("Usage: java RpfTocHandler <path to RPF directory>");
return;
}
Debug.init(System.getProperties());
RpfTocHandler toc = new RpfTocHandler();
if (!toc.loadFile(args[0])) {
Debug.output("RpfTocHandler: NOT read successfully!");
} else {
RpfTocEntry[] e = toc.getEntries();
Debug.output("For A.TOC: " + args[0]);
for (int i = 0; i < e.length; i++) {
Debug.output(e[i].toString());
}
}
System.exit(0);
}
}