// **********************************************************************
//
// <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/vpf/CoverageAttributeTable.java,v $
// $Revision: 1.8 $ $Date: 2005/08/09 19:29:39 $ $Author: dietrick $
// **********************************************************************
package com.bbn.openmap.layer.vpf;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.bbn.openmap.io.BinaryFile;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.util.DataBounds;
import com.bbn.openmap.util.Debug;
/**
* Handle the library level VPF directory. "noamer" in DCW is an example of the
* library level data. This class loads the associated tiling information, and
* the coverage types, and make them available to the client.
*/
public class CoverageAttributeTable {
/** the name of the library we are, for example "noamer" in DCW */
final protected String libraryname;
/** the path to our directory */
final protected String dirpath;
/** are we tiled or untiled coverage */
private boolean isTiled = false;
/** coverage name to CoverageEntry map */
final private Map<String, CoverageEntry> coverages = new HashMap<String, CoverageEntry>();
protected DataBounds bounds;
/**
* The tiles that compose our coverage area. The size of the array is going
* to be set to record count + 1, and the tiles will have their ID number as
* their index.
*/
private TileDirectory containedTiles[];
/** the column names in the cat. file */
private final static String CATColumns[] = {
Constants.CAT_COVNAME,
Constants.CAT_DESC,
Constants.CAT_LEVEL
};
/** expected schema types for the cat. file */
private final static char CATschematype[] = {
DcwColumnInfo.VPF_COLUMN_TEXT,
DcwColumnInfo.VPF_COLUMN_TEXT,
DcwColumnInfo.VPF_COLUMN_INT_OR_SHORT
};
/** expected schema lengths for cat. file */
private final static int CATschemalength[] = {
-1 /* 8 */,
-1 /* 50 */,
1
};
/**
* Construct a new coverage attribute table
*
* @param libname the name of the library
* @param dcwpath the path to the library
* @exception FormatException may throw FormatExceptions
*/
public CoverageAttributeTable(String dcwpath, String libname)
throws FormatException {
libraryname = libname;
dirpath = dcwpath + "/" + libraryname;
String cat = dirpath + "/cat";
if (!BinaryFile.exists(cat)) {
cat += ".";
}
DcwRecordFile rf = new DcwRecordFile(cat);
int catcols[] = rf.lookupSchema(CATColumns, true, CATschematype, CATschemalength, false);
List<Object> l = new ArrayList<Object>(rf.getColumnCount());
while (rf.parseRow(l)) {
int topL = ((Number) l.get(catcols[2])).intValue();
String desc = (String) l.get(catcols[1]);
String covtype = ((String) l.get(catcols[0])).toLowerCase().intern();
coverages.put(covtype, new CoverageEntry(topL, desc));
}
rf.close();
rf = null;
doTileRefStuff(dirpath + "/tileref");
}
/**
* is this library tiled
*
* @return <code>true</code> for tiled coverage. <code>false</code> else
*/
public final boolean isTiledCoverage() {
return isTiled;
}
/**
* the name of the library
*
* @return the name of the library
*/
public String getLibraryName() {
return libraryname;
}
/** the columns we need in fbr for tiling */
private static final String[] fbrColumns = {
Constants.FBR_XMIN,
Constants.FBR_YMIN,
Constants.FBR_XMAX,
Constants.FBR_YMAX
};
/** the columns we need in fcs for tiling */
private static final String[] fcsColumns = {
Constants.FCS_FEATURECLASS,
Constants.FCS_TABLE1,
Constants.FCS_TABLE1KEY,
Constants.FCS_TABLE2,
Constants.FCS_TABLE2KEY
};
/** the columns we need in fcs for tiling for DCW */
private static final String[] fcsColumnsDCW = {
Constants.FCS_FEATURECLASS,
Constants.FCS_TABLE1,
Constants.DCW_FCS_TABLE1KEY,
Constants.FCS_TABLE2,
Constants.DCW_FCS_TABLE2KEY
};
/**
* an internal function to load the tiling information
*
* @param pathname the path to the tile directory
*/
private void doTileRefStuff(String pathname) {
doTileRefStuff(pathname, false);
}
/**
* an internal function to load the tiling information, with an option to use
* DCW column names.
*
* @param pathname the path to the tile directory
* @param DCW use DCW column names.
*/
private void doTileRefStuff(String pathname, boolean DCW) {
String faceIDColumnName = null;
// Figure out how files names should be constructed...
boolean addSlash = true;
// if (pathname.endsWith(File.separator)) {
if (pathname.endsWith("/") || pathname.endsWith(File.separator)) {
addSlash = false;
}
// read fcs to figure out what column in tileref.aft we need
// to use to
// read the fbr (face bounding rectangle) table
try {
String fcsFile = pathname + (addSlash ? "/" : "") + "fcs";
if (!BinaryFile.exists(fcsFile)) {
fcsFile += ".";
}
DcwRecordFile fcs = new DcwRecordFile(fcsFile);
List<Object> fcsv = new ArrayList<Object>(fcs.getColumnCount());
int fcscols[];
if (!DCW) {
fcscols = fcs.lookupSchema(fcsColumns, true);
} else {
fcscols = fcs.lookupSchema(fcsColumnsDCW, true);
}
while (fcs.parseRow(fcsv)) {
String fclass = (String) fcsv.get(fcscols[0]);
String table1 = (String) fcsv.get(fcscols[1]);
String table1_key = (String) fcsv.get(fcscols[2]);
/*
* Not used String table2 = (String) fcsv.get(fcscols[3]); String
* table2_key = (String) fcsv.get(fcscols[4]);
*/
if ("tileref".equalsIgnoreCase(fclass) && "tileref.aft".equalsIgnoreCase(table1)) {
faceIDColumnName = table1_key.toLowerCase();
break;
}
}
fcs.close();
} catch (FormatException f) {
// If DCW, we'll get here, need to try lookupSchema with
// proper column names
if (!DCW)
doTileRefStuff(pathname, true);
return;
// either way, return. The recursive call may have worked.
} catch (NullPointerException npe) {
return; // file wasn't found...
}
if (faceIDColumnName == null) {
return; // won't be able to read the tiling info. abort
}
isTiled = true;
// Okay, we've got info on what column we use from tileref.aft
// to index into the fbr.
try {
DcwRecordFile aft = new DcwRecordFile(pathname + (addSlash ? "/" : "") + "tileref.aft");
int faceIDColumn = aft.whatColumn(faceIDColumnName.toLowerCase());
int tileNameColumn = aft.whatColumn("tile_name");
if ((faceIDColumn == -1) || (tileNameColumn == -1)) {
aft.close();
return;
}
String fbrFile = pathname + (addSlash ? "/" : "") + "fbr";
if (!BinaryFile.exists(fbrFile)) {
fbrFile += ".";
}
DcwRecordFile fbr = new DcwRecordFile(fbrFile);
int fbrIDColumn = fbr.whatColumn(Constants.ID);
List<Object> aftv = new ArrayList<Object>(aft.getColumnCount());
List<Object> fbrv = new ArrayList<Object>(fbr.getColumnCount());
int fbrcols[] = fbr.lookupSchema(fbrColumns, true);
// set the array size to record count + 1, to be able to
// use the tileID as the index into the array
// aft.getRecordCount() is not reliable if file is being
// read with a network input stream. So, we have to
// create the TileDirectory[] a different way.
// containedTiles = new TileDirectory[aft.getRecordCount()
// + 1];
// This is part of that solution...
ArrayList<Object> tileArrayList = new ArrayList<Object>(500);
Object nullTile = new Object();
while (aft.parseRow(aftv)) {
int fac_num = ((Number) aftv.get(faceIDColumn)).intValue();
fbr.getRow(fbrv, fac_num); // mutates fbrv
int tileid = ((Number) aftv.get(fbrIDColumn)).intValue();
String tilename = (String) aftv.get(tileNameColumn);
char chs[] = tilename.toCharArray();
boolean goodTile = false;
for (int i = 0; i < chs.length; i++) {
if ((chs[i] != '\\') && (chs[i] != ' ')) {
goodTile = true;
}
if (chs[i] == '\\') {
// chs[i] = File.separatorChar;
chs[i] = '/'; // we're using BinaryFile, in
// java land...
}
}
tilename = new String(chs);
// Part of the URL solution...
// This makes sure that the tileid can be used
// for the index. If the tile is not good, then
// nullTile will be set. If it is good, it will be
// replaced. This will end up putting nullTile at
// index 1 if the tileid is 1.
while (tileid > tileArrayList.size() - 1) {
tileArrayList.add(nullTile);
}
// End of solution addition part...
if (!goodTile) {
// Commenting out line is part of the solution,
// the
// spot is already marked with a nullTile object.
// containedTiles[tileid] = null;
continue;
}
float westlon = ((Number) fbrv.get(fbrcols[0])).floatValue();
float southlat = ((Number) fbrv.get(fbrcols[1])).floatValue();
float eastlon = ((Number) fbrv.get(fbrcols[2])).floatValue();
float northlat = ((Number) fbrv.get(fbrcols[3])).floatValue();
if (bounds == null) {
bounds = new DataBounds(westlon, southlat, eastlon, northlat);
} else {
bounds.add(westlon, southlat);
bounds.add(eastlon, northlat);
}
// Again, URL solution...
// containedTiles[tileid] = new
// TileDirectory(tilename, tileid,
// northlat, southlat,
// eastlon, westlon);
tileArrayList.set(tileid, new TileDirectory(tilename, tileid, northlat, southlat, eastlon, westlon));
}
aft.close();
fbr.close();
// And this is the resolution of the solution, taking
// the ArrayList and converting it to a TileDirectory
// array.
containedTiles = new TileDirectory[tileArrayList.size()];
Iterator<Object> it = tileArrayList.iterator();
int cnt = 0;
while (it.hasNext()) {
Object obj = it.next();
if (obj == nullTile) {
containedTiles[cnt++] = null;
} else {
containedTiles[cnt++] = (TileDirectory) obj;
}
}
} catch (FormatException f) {
// probably (hopefully?) untiled coverage...
containedTiles = null;
}
}
/**
* Get the description of a coverage type
*
* @param covname the name of the coverage type
* @return the coverage description from the VPF database. A null return
* value indicates an unknown coverage type
*/
public String getCoverageDescription(String covname) {
CoverageEntry ce = (CoverageEntry) coverages.get(covname);
return (ce == null) ? null : ce.getDescription();
}
/**
* Get the topology level of a coverage.
*
* @param covname the name of the coverage type
* @return the topology level of the coverage (-1 if not a valid coverage)
*/
public int getCoverageTopologyLevel(String covname) {
CoverageEntry ce = (CoverageEntry) coverages.get(covname);
return (ce == null) ? -1 : ce.getTopologyLevel();
}
/**
* Get the CoverageTable for a particular coverage type
*
* @param covname the name of the coverage type
* @return the associated coverage table (possibly null)
*/
public CoverageTable getCoverageTable(String covname) {
CoverageEntry ce = (CoverageEntry) coverages.get(covname);
if (ce != null) {
if (ce.getCoverageTable() == null) {
ce.setCoverageTable(new CoverageTable(dirpath, covname.intern(), this));
if (Debug.debugging("vpf")) {
Debug.output("Created new CoverageTable for " + covname + ": " + ce.description);
}
} else {
if (Debug.debugging("vpf")) {
Debug.output("Using cached CoverageTable for " + covname + ": " + ce.description);
}
}
return ce.getCoverageTable();
}
return null;
}
public CoverageTable getCoverageTableForFeature(String featureName) {
for (String key : coverages.keySet()) {
CoverageEntry ce = coverages.get(key);
Debug.output("CoverageTable: got " + ce + " for " + key);
CoverageTable ct = ce.getCoverageTable();
if (ct != null) {
if (ct.getFeatureClassInfo(featureName) != null) {
return ct;
}
} else {
Debug.output("no coverage table for " + ce);
}
}
return null;
}
/**
* get a list of tiles in the bounding region
*
* @param n northern boundary
* @param s southern boundary
* @param e eating foundry
* @param w wheat bread
* @return a vector of TileDirectories
*/
public List<TileDirectory> tilesInRegion(float n, float s, float e, float w) {
if (containedTiles == null) {
return null;
}
List<TileDirectory> retval = new ArrayList<TileDirectory>();
int numTiles = containedTiles.length;
for (int i = 0; i < numTiles; i++) {
TileDirectory tile = containedTiles[i];
if (tile != null && tile.inRegion(n, s, e, w)) {
retval.add(tile);
}
}
return retval;
}
/**
* Get the TileDirectory with the given ID number.
*/
public TileDirectory getTileWithID(int id) {
try {
return containedTiles[id];
} catch (ArrayIndexOutOfBoundsException aioobe) {
return null;
} catch (NullPointerException npe) {
return null;
}
}
/**
* Know that the tile id are the integers used in the tileref.aft file. May
* return null if the format of the id is bad, or if the tile doesn't really
* exist (that really shouldn't happen).
*/
public TileDirectory getTileWithID(String id) {
try {
return getTileWithID(Integer.parseInt(id));
} catch (NumberFormatException nfe) {
return null;
}
}
/**
* Find out if this library uses tiled data
*
* @return true for tiled data
*/
public boolean isTiledData() {
return (containedTiles != null);
}
/**
* Return the list of coverages this library has
*
* @return the list of coverages (DCW would include "po", "dn"; VMAP would
* have "bnd", "tran", etc.)
*/
public String[] getCoverageNames() {
return (String[]) coverages.keySet().toArray(Constants.EMPTY_STRING_ARRAY);
}
/**
* Gets a DataBounds object that specifies what the CAT covers.
*
* @return DataBounds
*/
public DataBounds getBounds() {
return bounds;
}
/**
* A utility class to hold information about one coverage type. Only the
* associated coverage table may be modified after construction.
*/
public static class CoverageEntry {
/** the VPF topology level of this coverage type */
private final int tLevel;
/** the VPF description string of this coverage type */
private final String description;
/** the CoverageTable for this coverage type */
private CoverageTable covtable;
/**
* Create a coverage entry without a coverage table
*
* @param topologyLevel the topology level for this coverageentry
* @param desc the description for this entry
*/
public CoverageEntry(int topologyLevel, String desc) {
this(topologyLevel, desc, null);
}
/**
* Create a coverage entry with an initial coverage table
*
* @param topologyLevel the topology level for this coverageentry
* @param desc the description for this entry
* @param covtable the coveragetable for this entry
*/
public CoverageEntry(int topologyLevel, String desc, CoverageTable covtable) {
this.tLevel = topologyLevel;
this.description = desc;
this.covtable = covtable;
}
/**
* Get the topology level for this entry
*/
public int getTopologyLevel() {
return tLevel;
}
/**
* Get the description for this entry
*/
public String getDescription() {
return description;
}
/**
* Get the associated coveragetable
*/
public CoverageTable getCoverageTable() {
return covtable;
}
/**
* Set the associated coveragetable
*
* @param covtable the new coveragetable
*/
/* package */void setCoverageTable(CoverageTable covtable) {
this.covtable = covtable;
}
}
}