// **********************************************************************
//
// <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/RpfFrameCacheHandler.java,v $
// $RCSfile: RpfFrameCacheHandler.java,v $
// $Revision: 1.13 $
// $Date: 2007/02/26 17:34:04 $
// $Author: dietrick $
//
// **********************************************************************
/**
* Modifications:
*
* 1. Changed getCoverage()
*
*/
package com.bbn.openmap.layer.rpf;
import java.util.List;
import java.util.Vector;
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;
/**
* The RpfFrameCacheHandler does everything involved with handling RAW RPF
* frames. If used locally, it can also deal with filling the role of
* RpfFrameProvider. You create one of these with the paths to the RPF
* directories, and then hand it to something that needs a RpfFrameProvider, or
* that acts like one.
*/
public class RpfFrameCacheHandler
extends CacheHandler
implements RpfFrameProvider {
/* Default frame cache size. */
public final static int FRAME_CACHE_SIZE = 5;
/** Colortable used on the frames. */
protected RpfColortable colortable;
/** For future use... */
protected boolean Dchum = true;
/** Special outlining for chummed subframes */
protected boolean outlineChum = false;
/** The Table of Contents files, parsed and ready to use. */
protected RpfTocHandler[] tocs;
/** View and display attributes for the data. */
protected RpfViewAttributes viewAttributes = new RpfViewAttributes();
/**
* The default constructor.
*
* @param RpfPaths the directory paths to the RPF directories.
*/
public RpfFrameCacheHandler(String[] RpfPaths) {
this(RpfPaths, FRAME_CACHE_SIZE);
}
/**
* The constructor to use if you want to modify the number of frames held in
* the cache..
*
* @param RpfPaths the directory paths to the RPF directories.
*/
public RpfFrameCacheHandler(String[] RpfPaths, int max_size) {
super(max_size);
tocs = createTocHandlers(RpfPaths);
colortable = new RpfColortable();
}
/**
* When you pre-initialize the RpfTocHandlers before giving them to the
* RpfFrameCacheHandler.
*/
public RpfFrameCacheHandler(RpfTocHandler[] tocHandlers) {
tocs = tocHandlers;
colortable = new RpfColortable();
}
// public void finalize() {
// Debug.message("gc", "RpfFrameCacheHandler: getting GC'd");
// }
/**
* RpfFrameProvider interface method. If this is being used as a frame
* provider, it's local, right?
*/
public boolean needViewAttributeUpdates() {
return false;
}
/**
* Should only be set via the object it is sending frame data to. Don't send
* in a null value, since this is assumed to be valid in other parts of the
* code.
*/
public void setViewAttributes(RpfViewAttributes va) {
viewAttributes = va;
if (va != null && colortable != null) {
colortable.setOpaqueness(va.opaqueness);
colortable.setNumColors(va.numberOfColors);
}
}
/**
* RpfFrameProvider interface method. Return all the RpfCoverageBoxes that
* fall in the area of interest.
*
* @param ullat NW latitude.
* @param ullon NW longitude.
* @param lrlat SE latitude.
* @param lrlon SE longitude
* @param proj projection to use for zone decisions.
* @param chartSeries RpfProductInfo.seriesCode entry.
* @return Vector of RpfCoverageBoxes.
*/
public Vector<RpfCoverageBox> getCatalogCoverage(double ullat, double ullon, double lrlat, double lrlon, Projection proj, String chartSeries) {
Vector<RpfCoverageBox> coverages = new Vector<RpfCoverageBox>();
for (RpfTocHandler toc : tocs) {
// Check the tochandlers for differences, and reload them
// if necessary.
if (toc.hasChanged())
toc.reload();
if (!toc.isValid())
continue;
toc.getCatalogCoverage(ullat, ullon, lrlat, lrlon, proj, chartSeries, coverages);
}
return coverages;
}
/**
* Given an area and a two-letter chart series code, find the percentage of
* coverage on the map that that chart series can offer. If you want specific
* coverage information, use the getCatalogCoverage call. Don't send a chart
* series code of ANY, since that doesn't make sense.
*
* @param ullat NW latitude.
* @param ullon NW longitude.
* @param lrlat SE latitude.
* @param lrlon SE longitude
* @param p projection to use for zone decisions.
* @param chartSeries RpfProductInfo.seriesCode entry.
* @return percentage of map covered by specific chart type.
* @see #getCatalogCoverage
*/
public float getCalculatedCoverage(double ullat, double ullon, double lrlat, double lrlon, Projection p, String chartSeries) {
if (chartSeries.equalsIgnoreCase(RpfViewAttributes.ANY)) {
return 0f;
}
Vector<RpfCoverageBox> results = getCatalogCoverage(ullat, ullon, lrlat, lrlon, p, chartSeries);
int size = results.size();
if (size == 0) {
return 0f;
}
// Now interpret the results and figure out the real total
// percentage coverage for the chartSeries. First need to
// figure out the current size of the subframes. Then create
// a boolean matrix of those subframes that let you figure out
// how many of them are available. Calculate the percentage
// off that.
int i, x, y;
double frameLatInterval = Double.MAX_VALUE;
double frameLonInterval = Double.MAX_VALUE;
RpfCoverageBox rcb;
for (i = 0; i < size; i++) {
rcb = (RpfCoverageBox) results.elementAt(i);
if (rcb.subframeLatInterval < frameLatInterval) {
frameLatInterval = rcb.subframeLatInterval;
}
if (rcb.subframeLonInterval < frameLonInterval) {
frameLonInterval = rcb.subframeLonInterval;
}
}
if (frameLatInterval == Double.MAX_VALUE || frameLonInterval == Double.MAX_VALUE) {
return 0.0f;
}
int numHFrames = (int) Math.ceil((lrlon - ullon) / frameLonInterval);
int numVFrames = (int) Math.ceil((ullat - lrlat) / frameLatInterval);
boolean[][] coverage = new boolean[numHFrames][numVFrames];
for (i = 0; i < size; i++) {
rcb = (RpfCoverageBox) results.elementAt(i);
if (rcb.percentCoverage == 100) {
return 1.0f;
}
for (y = 0; y < numVFrames; y++) {
for (x = 0; x < numHFrames; x++) {
// degree location of indexs
float yFrameLoc = (float) (lrlat + (y * frameLatInterval));
float xFrameLoc = (float) (ullon + (x * frameLonInterval));
if (coverage[x][y] == false) {
if (rcb.within(yFrameLoc, xFrameLoc)) {
coverage[x][y] = true;
}
}
}
}
}
float count = 0;
for (y = 0; y < numVFrames; y++) {
for (x = 0; x < numHFrames; x++) {
if (coverage[x][y] == true) {
// System.out.print("X");
count++;
} else {
// System.out.print(".");
}
}
// System.out.println();
}
return count / (float) (numHFrames * numVFrames);
}
/**
* Given a projection which describes the map (or area of interest), return
* the best RpfTocEntry, from all the A.TOC, that covers the area.
* RpfFrameProvider method.
*
* 1. Fixed bug that was preventing it from actually returning the best
* coverage box in the first position of the Vector. 2. Prevented the method
* from returning early when it found a box that claimed perfect coverage.
* This was causing problems in some areas, where boxes that claimed coverage
* didn't actually have it. This could be a data problem, so maybe we can add
* the early return back in in the future. 3. Changed < to <= below. 4.
* Iterates thru list returned by RpcTocHandler since the return type for
* getBestCoverageEntry changed from RpfTocEntry to List.
*
* @param ullat NW latitude.
* @param ullon NW longitude.
* @param lrlat SE latitude.
* @param lrlon SE longitude
* @param proj CADRG projection to use for zone decisions.
* @return Vector of RpfCoverageBoxes.
*/
public Vector<RpfCoverageBox> getCoverage(double ullat, double ullon, double lrlat, double lrlon, Projection proj) {
Debug.message("rpf", "RpfFrameCacheHandler: getCoverage()");
Vector<RpfCoverageBox> coverageBoxes = new Vector<RpfCoverageBox>();
int count = -1;
for (RpfTocHandler toc : tocs) {
count++;
// Check the toc handlers for differences, and reload them
// if necessary.
if (toc.hasChanged())
toc.reload();
if (!toc.isValid())
continue;
// NOTE: returning a list of best coverage entries.
// Added inner loop to deal with iterating over list of
// results.
List<RpfTocEntry> coverageEntries = toc.getBestCoverageEntry(ullat, ullon, lrlat, lrlon, proj, viewAttributes);
for (RpfTocEntry currentEntry : coverageEntries) {
// This is a test for total coverage. If we get total
// coverage of an exact scale match, just return this
// coverage box right away. If the scale is not a
// perfect
// match, then we will return the box that has
// complete
// coverage with the best scale. A boundaryHit of 8
// means
// total coverage. Trust me.
if (currentEntry != null) {
if (Debug.debugging("rpftoc")) {
System.out.println("RFCH: Toc " + count + " returned an entry");
}
RpfCoverageBox currentCoverage = currentEntry.coverage;
// NOTE:
// removed because some areas had boxes that
// claimed perfect
// coverage but didn't actually have it.
// This could be a data problem, but in any case,
// this work-around returns all relevant coverage
// boxes.
// if (currentCoverage.percentCoverage >= 100f &&
// scaleDifference(proj, currentCoverage) == 0) {
// coverageBoxes.removeAllElements();
// coverageBoxes.addElement(currentCoverage);
// return coverageBoxes;
// } else {
// You now ought to at least make sure that the
// scales are the same for all A.TOCs. That way,
// the subframe spacing will be the same. Put the
// best coverage (smallest scale difference) at
// the front of the list, and whittle it down from
// there.
Object[] coverageArray = new Object[coverageBoxes.size()];
coverageBoxes.copyInto(coverageArray);
coverageBoxes.removeAllElements();
int size = coverageArray.length;
// Set this here in case the vector is empty...
// float currentScale = currentEntry.info.scale;
if (size == 0) {
coverageBoxes.addElement(currentCoverage);
} else {
boolean addedCurrent = false;
boolean okToAddCurrent = true;
for (int j = 0; j < size; j++) {
RpfCoverageBox rcb = (RpfCoverageBox) coverageArray[j];
if (!addedCurrent) {
if (j == 0) {
// So first, check to see if the
// current
// coverage is a better match than
// the
// current best, first considering
// scale, and then considering
// coverage.
// NOTE:
// Changed < to <=, because <
// gives
// higher priority to whatever is
// first in the
// list. I.e. if 2 boxes had the
// same
// scale difference but the second
// one actually
// had better coverage, it would
// have gotten
// discarded.
if (scaleDifference(proj, currentCoverage) <= scaleDifference(proj, rcb)
&& currentCoverage.percentCoverage >= rcb.percentCoverage) {
coverageBoxes.addElement(currentCoverage);
addedCurrent = true;
if (currentCoverage.scale == rcb.scale) {
coverageBoxes.addElement(rcb);
} else {
// if these scales don't
// match, none of the
// subsequent rcbs will
// either. We're done.
break;
}
} else { // rcb percent coverage
// is better
coverageBoxes.addElement(rcb);
}
} else { // j!= 0
if (currentCoverage.percentCoverage >= rcb.percentCoverage) {
if (((RpfCoverageBox) coverageBoxes.get(0)).scale == currentCoverage.scale) {
coverageBoxes.add(currentCoverage);
addedCurrent = true;
} else {
okToAddCurrent = false;
}
coverageBoxes.add(rcb);
} else { // rcb percent coverage
// is better
// we already know that scale
// matches since it was
// added previously
coverageBoxes.add(rcb);
}
}
} else { // currentCoverage already added;
// all we need to do is add current
// rcb if scale matches
if (((RpfCoverageBox) coverageBoxes.get(0)).scale == rcb.scale) {
coverageBoxes.add(rcb);
} else {
}
}
}
// Add current if not added already and if
// scale matches.
// It's possible that we performed this check
// already --
// i.e. if currentCoverage had better %
// coverage
// than someone else but wasn't added because
// of the scale.
// If that happened, then okToAddCurrent is
// false.
if (!addedCurrent && okToAddCurrent) {
if (((RpfCoverageBox) coverageBoxes.get(0)).scale == currentCoverage.scale) {
coverageBoxes.add(currentCoverage);
} else {
}
}
}
// }
} else {
if (Debug.debugging("rpftoc")) {
System.out.println("RFCH: Toc " + count + " did NOT return an entry");
}
}
}
}
return coverageBoxes;
}
/**
* Given the indexes to a certain RpfTocEntry within a certain A.TOC, find
* the frame and return the attribute information. The tocNumber and
* entryNumber are given within the RpfCoverageBox received from a
* getCoverage call.
*
* @param tocNumber the toc id for a RpfTocHandler for a particular frame
* provider.
* @param entryNumber the RpfTocEntry id for a RpfTocHandler for a particular
* frame provider.
* @param x the horizontal subframe index, from the left side of a boundary
* rectangle of the entry.
* @param y the vertical subframe index, from the top side of a boundary
* rectangle of the entry.
* @see #getCoverage
* @return string.
*/
public String getSubframeAttributes(int tocNumber, int entryNumber, int x, int y) {
if (!tocs[tocNumber].isValid())
return null;
RpfTocEntry entry = tocs[tocNumber].entries[entryNumber];
/* If beyond the image boundary, forget it */
if (y < 0 || x < 0 || entry == null || y >= entry.vertFrames * 6 || x >= entry.horizFrames * 6) {
return null;
}
if (!entry.isFramesLoaded()) {
tocs[tocNumber].loadFrameInformation(entry);
}
RpfFrameEntry frameEntry = entry.getFrame(y / 6, x / 6);
/* Get the right frame from the frame cache */
RpfFrame frame = (RpfFrame) get(frameEntry);
if (frame == null)
return null;
/*
* This should never fail, since all subframes should be present
*/
return frame.getReport(x, y, frameEntry, entry.Cib);
}
/**
* Given the indexes to a certain RpfTocEntry within a certain A.TOC, find
* the frame/subframe data, decompress it, and return image pixels. The
* tocNumber and entryNumber are given within the RpfCoverageBox received
* from a getCoverage call.
*
* @param tocNumber the toc id for a RpfTocHandler for a particular frame
* provider.
* @param entryNumber the RpfTocEntry id for a RpfTocHandler for a particular
* frame provider.
* @param x the horizontal subframe index, from the left side of a boundary
* rectangle of the entry.
* @param y the vertical subframe index, from the top side of a boundary
* rectangle of the entry.
* @see #getCoverage
* @return integer pixel data.
*/
public int[] getSubframeData(int tocNumber, int entryNumber, int x, int y) {
if (!tocs[tocNumber].isValid()) {
return null;
}
RpfTocEntry entry = tocs[tocNumber].entries[entryNumber];
/* If beyond the image boundary, forget it */
if (y < 0 || x < 0 || entry == null || y >= entry.vertFrames * 6 || x >= entry.horizFrames * 6) {
return null;
}
if (!entry.isFramesLoaded()) {
tocs[tocNumber].loadFrameInformation(entry);
}
RpfFrameEntry frameEntry = entry.getFrame(y / 6, x / 6);
/* Get the right frame from the frame cache */
RpfFrame frame = (RpfFrame) get(frameEntry);
if (frame == null) {
return null;
}
checkColortable(frame, frameEntry, entry, tocNumber, entryNumber);
/*
* This should never fail, since all subframes should be present
*/
return frame.decompressSubframe(x, y, colortable);
}
public RpfIndexedImageData getRawSubframeData(int tocNumber, int entryNumber, int x, int y) {
if (!tocs[tocNumber].isValid()) {
return null;
}
RpfTocEntry entry = tocs[tocNumber].entries[entryNumber];
/* If beyond the image boundary, forget it */
if (y < 0 || x < 0 || entry == null || y >= entry.vertFrames * 6 || x >= entry.horizFrames * 6) {
return null;
}
if (!entry.isFramesLoaded()) {
tocs[tocNumber].loadFrameInformation(entry);
}
RpfFrameEntry frameEntry = entry.getFrame(y / 6, x / 6);
/* Get the right frame from the frame cache */
RpfFrame frame = (RpfFrame) get(frameEntry);
if (frame == null)
return null;
checkColortable(frame, frameEntry, entry, tocNumber, entryNumber);
RpfIndexedImageData riid = new RpfIndexedImageData();
riid.imageData = frame.decompressSubframe(x, y);
riid.colortable = colortable.colors;
return riid;
}
/**
* Take a bunch of stuff that has already been calculated, and then figure
* out if a new colortable is needed. If it is, load it up with info. Called
* from two different places, which is why it exists.
*
* It's been determined that, for each subframe, the colortable from it's
* parent frame should be used. Although RPF was designed and specified that
* the colortable should be constant across zones, that's not always the
* case.
*/
protected void checkColortable(RpfFrame frame, RpfFrameEntry frameEntry, RpfTocEntry entry, int tocNumber, int entryNumber) {
// Colortables are constant across chart types and zones. If
// the current chart type and zone don't match the colortable,
// read the proper one from the frame. All the frames inside
// an entry, which is a boundary box, will certainly share a
// colortable.
// if (colortable.colors == null ||
// !colortable.isSameATOCIndexes(tocNumber, entryNumber)) {
// You know, we don't need to make the check - we should just
// do this every time - the colortable is already created for
// the frame, so we might as well use what we know to be good
// for each subframe.
if (true) {
if (Debug.debugging("rpf")) {
Debug.output("RpfFrameCacheHandler: getting new colors");
Debug.output("RpfFrameCacheHandler: getting CIB colors = " + entry.Cib);
}
// Seems like there ought to be a better way to do this.
colortable.setFrom(frame.getColortable());
colortable.setCib(entry.Cib);
colortable.setATOCIndexes(tocNumber, entryNumber);
colortable.zone = entry.zone;
colortable.seriesCode = entry.info.seriesCode;
}
if (viewAttributes != null) {
// this is useless...
// colortable.setNumColors(viewAttributes.numberOfColors);
colortable.setOpaqueness(viewAttributes.opaqueness);
}
}
/**
* Set up the A.TOC files, to find out what coverage there is.
*
* @param RpfPaths the paths to the RPF directories.
* @return the RpfTocHandlers for the A.TOCs.
*/
public static RpfTocHandler[] createTocHandlers(String[] RpfPaths) {
RpfTocHandler[] tocs = new RpfTocHandler[(RpfPaths != null ? RpfPaths.length : 0)];
if (RpfPaths != null) {
for (int i = 0; i < tocs.length; i++) {
tocs[i] = new RpfTocHandler(RpfPaths[i], i);
}
}
return tocs;
}
/**
* A customized way to retrieve a frame from the cache, using a
* RpfFrameEntry. A RpfFrameEntry is the way to get the Dchum capability
* kicked off in the frame. If you don't care about Dchum, use the other get
* method. CacheHandler method.
*/
public Object get(RpfFrameEntry rfe) {
CacheObject ret = searchCache(rfe.framePath);
if (ret != null)
return ret.obj;
ret = load(rfe);
if (ret == null)
return null;
if (Debug.debugging("rpfdetail")) {
System.out.println(rfe);
}
replaceLeastUsed(ret);
return ret.obj;
}
public CacheObject load(Object key) {
if (key instanceof RpfFrameEntry) {
RpfFrameEntry rfe = (RpfFrameEntry) key;
if (!rfe.exists) {
if (Debug.debugging("rpf")) {
System.out.println("RpfFrameCacheHandler: Frame doesn't exist!: " + rfe.framePath);
}
return null;
}
if (Debug.debugging("rpf")) {
Debug.output("RpfFrameCacheHandler: Loading Frame " + rfe.framePath);
}
RpfFrame frame = new RpfFrame(rfe);
if (frame.isValid()) {
return new CacheObject(rfe.framePath, frame);
} else {
if (Debug.debugging("rpf")) {
Debug.error("RpfFrameCacheHandler: Couldn't find frame /" + rfe.framePath + "/ (" + rfe.framePath.length()
+ " chars)");
}
rfe.exists = false;
}
}
return null;
}
/**
* Cachehandler method.
*/
public void resizeCache(int max_size) {
resetCache(max_size);
}
/**
* CacheHandler method. Need to clear memory, get gc moving, and ready for
* new objects
*/
public void resetCache() {
super.resetCache();
Debug.message("rpf", "RpfFrameCacheHandler: reset frame cache.");
}
public static float scaleDifference(Projection proj, RpfCoverageBox box) {
return (float) (Math.abs(proj.getScale() - box.scale));
}
public RpfColortable getColortable() {
return colortable;
}
public void setColortable(RpfColortable colortable) {
this.colortable = colortable;
}
}