// **********************************************************************
//
// <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/CoverageTable.java,v $
// $Revision: 1.13 $ $Date: 2005/12/09 21:08:58 $ $Author: dietrick $
// **********************************************************************
package com.bbn.openmap.layer.vpf;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.bbn.openmap.io.BinaryFile;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.layer.vpf.VPFAutoFeatureGraphicWarehouse.FeaturePriorityHolder;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
/**
* Encapsulate a VPF coverage directory. This class handles requests that happen
* for a particular coverage type (political boundary, road, etc.) for a
* particular library (north america, browse, etc.).
*/
public class CoverageTable {
protected final static Logger logger = Logger.getLogger("com.bbn.openmap.layer.vpf.CoverageTable");
/** our coverage type - such as "po", "bnd", "hydro" */
final public String covtype;
/** the directory for our coverage type */
final protected String tablepath;
/** a table to cache int.vdt information */
final private Map<CoverageIntVdt, String> intvdtrec = new HashMap<CoverageIntVdt, String>();
/** a table to cache char.vdt information */
final private Map<CoverageCharVdt, String> charvdtrec = new HashMap<CoverageCharVdt, String>();
/** hack - used by EdgeTable */
public int cachedLineSchema[] = null;
/** hack - used by AreaTable */
public int cachedAreaSchema[] = null;
/** hack - used by TextTable */
public int cachedTextSchema[] = null;
/** hack - used by nodetable */
public int cachedEPointSchema[] = null;
/** hack - used by nodetable */
public int cachedCPointSchema[] = null;
/** featureclasses used for the line feature type */
public FeatureClassInfo lineinfo[] = new FeatureClassInfo[0];
/** featureclasses used for the area feature type */
public FeatureClassInfo areainfo[] = new FeatureClassInfo[0];
/** featureclasses used for the text feature type */
public FeatureClassInfo textinfo[] = new FeatureClassInfo[0];
/** featureclasses used for the entity node feature type */
public FeatureClassInfo epointinfo[] = new FeatureClassInfo[0];
/** featureclasses used for the connected node feature type */
public FeatureClassInfo cpointinfo[] = new FeatureClassInfo[0];
/**
* Feature classes to look up FeatureClassInfo via feature name.
*/
protected Hashtable<String, FeatureClassInfo> featureTypes = new Hashtable<String, FeatureClassInfo>();
protected Hashtable<String, FeatureClassRec> featureTypeInfo;
/** do we need to append a '.' to three-character file names */
public boolean appendDot = false;
/**
* Need this in case we have to go from the coverage type-<feature
* type-<tile
*/
protected CoverageAttributeTable cat;
/** hackage for the antarctica polygon in DCW browse coverage */
final public boolean doAntarcticaWorkaround;
public static final char EDGE_FEATURETYPE = 'L';
public static final char AREA_FEATURETYPE = 'A';
public static final char TEXT_FEATURETYPE = 'T';
public static final char UPOINT_FEATURETYPE = 'P';
public static final char EPOINT_FEATURETYPE = 'E';
public static final char CPOINT_FEATURETYPE = 'N';
public static final char COMPLEX_FEATURETYPE = 'C';
public static final char SKIP_FEATURETYPE = 'S';
/**
* Construct a CoverageTable object. Data is expected to be in a directory
* called path/covtype.
*
* @param path the path to the parent directory of where our data resides
* @param covtype the subdirectory name for the coverage data
*/
public CoverageTable(String path, String covtype) {
this.covtype = covtype;
tablepath = path + "/" + covtype + "/";
doAntarcticaWorkaround = (tablepath.indexOf("browse") >= 0);
internSchema();
loadIntVDT();
loadCharVDT();
featureTypeInfo = getFeatureTypeInfo();
}
/**
* Construct a CoverageTable object. Data is expected to be in a directory
* called path/covtype.
*
* @param path the path to the parent directory of where our data resides
* @param covtype the subdirectory name for the coverage data
* @param cat the CoverageAttributeTable reference, in case we need to
* backtrack the tiles through the feature tables.
*/
public CoverageTable(String path, String covtype, CoverageAttributeTable cat) {
this(path, covtype);
this.cat = cat;
}
/** required column names for char.vdt and int.vdt files */
public final static String VDTColumnNames[] = { Constants.VDT_TABLE, Constants.VDT_ATTRIBUTE,
Constants.VDT_VALUE, Constants.VDT_DESC };
/** expected schema types for int.vdt files */
public final static char intVDTschematype[] = { DcwColumnInfo.VPF_COLUMN_INT,
DcwColumnInfo.VPF_COLUMN_TEXT, DcwColumnInfo.VPF_COLUMN_TEXT,
DcwColumnInfo.VPF_COLUMN_INT_OR_SHORT, DcwColumnInfo.VPF_COLUMN_TEXT };
/** expected schema lengths for int.vdt files */
public final static int intVDTschemalength[] = { 1, -1, -1, 1, -1 };
private void loadIntVDT() {
try {
String vdt = tablepath + Constants.intVDTTableName;
if (BinaryFile.exists(vdt)) {
DcwRecordFile intvdt = new DcwRecordFile(vdt);
int cols[] = intvdt.lookupSchema(VDTColumnNames, true, intVDTschematype, intVDTschemalength, false);
for (List<Object> l = new ArrayList<Object>(intvdt.getColumnCount()); intvdt.parseRow(l);) {
String tab = (String) l.get(cols[0]);
String attr = (String) l.get(cols[1]);
int val = ((Number) l.get(cols[2])).intValue();
String desc = ((String) l.get(cols[3])).intern();
intvdtrec.put(new CoverageIntVdt(tab, attr, val), desc);
}
intvdt.close();
}
} catch (FormatException f) {
}
}
/** expected schema types for char.vdt files */
public final static char charVDTschematype[] = { DcwColumnInfo.VPF_COLUMN_INT,
DcwColumnInfo.VPF_COLUMN_TEXT, DcwColumnInfo.VPF_COLUMN_TEXT,
DcwColumnInfo.VPF_COLUMN_TEXT, DcwColumnInfo.VPF_COLUMN_TEXT };
/** expected schema lengths for char.vdt files */
public final static int charVDTschemalength[] = { 1, -1, -1, -1, -1 };
private void loadCharVDT() {
try {
String vdt = tablepath + Constants.charVDTTableName;
if (BinaryFile.exists(vdt)) {
DcwRecordFile charvdt = new DcwRecordFile(vdt);
int cols[] = charvdt.lookupSchema(VDTColumnNames, true, charVDTschematype, charVDTschemalength, false);
for (List<Object> l = new ArrayList<Object>(charvdt.getColumnCount()); charvdt.parseRow(l);) {
String tab = (String) l.get(cols[0]);
String attr = (String) l.get(cols[1]);
String val = (String) l.get(cols[2]);
String desc = ((String) l.get(cols[3])).intern();
charvdtrec.put(new CoverageCharVdt(tab, attr, val), desc);
}
charvdt.close();
}
} catch (FormatException f) {
}
}
private FeatureClassInfo[] internSchema(FeatureClassInfo[] fti, String foreign_key,
String tablename) throws FormatException {
FeatureClassInfo rv[] = new FeatureClassInfo[fti.length + 1];
System.arraycopy(fti, 0, rv, 0, fti.length);
rv[fti.length] = new FeatureClassInfo(this, foreign_key.intern(), tablepath, tablename.intern());
return rv;
}
/** the columns of the fcs file we are interested in */
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 };
/**
* This method reads the feature class schema (fcs) file to discover the
* inter-table relations (joins, in database parlance). As a side effect,
* this method also sets the appendDot member.
*/
private void internSchema() {
internSchema(false);
}
/**
* This method reads the feature class schema (fcs) file to discover the
* inter-table relations (joins, in database parlance). As a side effect,
* this method also sets the appendDot member. The DCW option refers to if
* the DCW column names should be used, for DCW data. This is only true if a
* problem occurs, and then this method is called recursively.
*/
private void internSchema(boolean DCW) {
// Figure out how files names should be constructed...
boolean addSlash = true;
// if (tablepath.endsWith(File.separator)) {
if (tablepath.endsWith("/") || tablepath.endsWith(File.separator)) {
addSlash = false;
}
try {
String filename = tablepath + (addSlash ? "/" : "") + "fcs";
if (!BinaryFile.exists(filename)) {
filename += ".";
appendDot = true;
}
DcwRecordFile fcs = new DcwRecordFile(filename);
int[] fcscols = fcs.lookupSchema(DCW ? fcsColumnsDCW : fcsColumns, true);
for (List<Object> fcsrec = new ArrayList<Object>(fcs.getColumnCount()); fcs.parseRow(fcsrec);) {
String feature_class = (String) fcsrec.get(fcscols[0]);
String table1 = (String) fcsrec.get(fcscols[1]);
String foreign_key = (String) fcsrec.get(fcscols[2]);
String table2 = (String) fcsrec.get(fcscols[3]);
String primary_key = (String) fcsrec.get(fcscols[4]);
internSchema(feature_class.toLowerCase(), table1.toLowerCase(), foreign_key.toLowerCase(), table2.toLowerCase(), primary_key.toLowerCase());
}
fcs.close();
} catch (FormatException f) {
if (!DCW) {
internSchema(true);
} else {
System.out.println("CoverageTable: " + f.getMessage());
}
}
}
private void internSchema(String feature_class, String table1, String foreign_key,
String table2, String primary_key) {
try {
if (table1.equals("fac")) {
areainfo = internSchema(areainfo, foreign_key, table2);
} else if (table1.equals("edg")) {
lineinfo = internSchema(lineinfo, foreign_key, table2);
} else if (table1.equals("end")) {
epointinfo = internSchema(epointinfo, foreign_key, table2);
} else if (table1.equals("cnd")) {
cpointinfo = internSchema(cpointinfo, foreign_key, table2);
} else if (table1.equals("txt")) {
textinfo = internSchema(textinfo, foreign_key, table2);
} else if (table1.startsWith(feature_class)
&& (foreign_key.equals("end_id") || foreign_key.equals("cnd_id")
|| foreign_key.equals("fac_id") || foreign_key.equals("edg_id") || foreign_key.equals("txt_id"))) {
if (Debug.debugging("vpf")) {
Debug.output("CoverageTable: Found entry for: " + feature_class + ": " + table1
+ "|" + foreign_key + "|" + table2 + "|" + primary_key);
}
FeatureClassInfo featureClass = new FeatureClassInfo(this, foreign_key.intern(), tablepath.intern(), table1.intern(), table2.intern(), foreign_key.intern());
featureClass.close(false); // releases file descriptors
featureTypes.put(feature_class.intern(), featureClass);
} else {
// nothing else that we care about for now
// symbol.rat could show up here, for example
}
} catch (FormatException f) {
System.out.println("internSchema: " + f.getMessage());
}
}
/**
* Get the path for this coverage
*/
public String getDataPath() {
return tablepath;
}
/**
* Returns all the feature classes
*/
public Map<String, FeatureClassInfo> getFeatureClasses() {
return Collections.unmodifiableMap(featureTypes);
}
/**
* Returns the FeatureClassInfo object corresponding to the feature type.
* Returns null if the featureType doesn't exist.
*
* @return the feature class object for the feature type
* @param featureType the name of the feature to get
*/
public FeatureClassInfo getFeatureClassInfo(String featureType) {
return featureTypes.get(featureType);
}
public String getDescription(String t, String a, int v) {
CoverageIntVdt civ = new CoverageIntVdt(t, a, v);
return intvdtrec.get(civ);
}
public String getDescription(String t, String a, String v) {
CoverageCharVdt civ = new CoverageCharVdt(t, a, v);
return charvdtrec.get(civ);
}
private String getDescription(List<Object> id, FeatureClassInfo fti[], MutableInt ret) {
if ((fti == null) || (fti.length == 0)) {
return null;
}
StringBuffer foo = null;
for (int i = 0; i < fti.length; i++) {
String desc = fti[i].getDescription(id, ret);
if (desc != null) {
if (foo == null) {
foo = new StringBuffer(desc);
} else {
foo.append(";; ").append(desc);
}
}
}
return ((foo == null) ? null : foo.toString());
}
public String getLineDescription(List<Object> lineid, MutableInt retval) {
return getDescription(lineid, lineinfo, retval);
}
public String getTextDescription(List<Object> textid, MutableInt retval) {
return getDescription(textid, textinfo, retval);
}
public String getEPointDescription(List<Object> pointid, MutableInt retval) {
return getDescription(pointid, epointinfo, retval);
}
public String getCPointDescription(List<Object> pointid, MutableInt retval) {
return getDescription(pointid, cpointinfo, retval);
}
public String getAreaDescription(List<Object> areaid, MutableInt retval) {
return getDescription(areaid, areainfo, retval);
}
public void setCoverateAttributeTable(CoverageAttributeTable cat) {
this.cat = cat;
}
public CoverageAttributeTable getCoverageAttributeTable() {
return cat;
}
/**
* Given a tile directory, go through the entries in the edg/fac/txt files,
* and send those entries to the warehouse. The warehouse will check their
* feature names with the feature names given to it in its properties, and
* eliminate the ones that it shouldn't draw.
*/
public void drawTile(TileDirectory drawtd, VPFGraphicWarehouse warehouse, LatLonPoint ll1,
LatLonPoint ll2, float dpplat, float dpplon) {
if (Debug.debugging("vpf.tile")) {
Debug.output("Drawtile for " + drawtd);
}
TableHolder tables = new TableHolder(this);
tables.drawTile(drawtd, warehouse, ll1, ll2, dpplat, dpplon);
}
/**
* This function uses the warehouse to get a list of features, and then
* looks in the featureList to see what feature tables handle those
* features. Using the appropriate feature table, the function then tracks
* down the tile that contains that feature, and the feature index into that
* tile file, and then contacts the warehouse to get that feature created
* into a graphic.
*/
public boolean drawFeatures(VPFFeatureWarehouse warehouse, LatLonPoint ll1, LatLonPoint ll2,
float dpplat, float dpplon) {
boolean didSomething = false;
TableHolder tables = new TableHolder(this);
// First, find the list of features, and iterate through
// them. Use each one to go through it's feature table, if it
// exists. Then, from the feature table (perhaps check the
// FACC code, too), get the tile number and feature ID.
// Access the CoverageAttributeTable to get the tile (using a
// table from above), and check if it is within bounds. If it
// is, seek to get the feature. Once the feature has been
// read, contact the warehouse to use the row to build a
// graphic. Hold on to the tile in case (and it's likely)
// that the next feature desired is in the same tile). If it
// isn't, then dump the tile.
// Should sort the feature types by area, text and then lines,
// to do them in that order.
List<String> featureList = warehouse.getFeatures();
for (String currentFeature : featureList) {
// Figure out if the feature should be rendered, depending
// on what the warehouse settings are (drawedges,
// drawareas, drawtext).
char featureType = whatFeatureType(warehouse, currentFeature);
if (featureType == SKIP_FEATURETYPE) {
// Blow off this feature type.
continue;
}
if (Debug.debugging("vpf")) {
Debug.output("CoverageTable getting " + currentFeature + " features");
}
// Get the feature class for this feature type.
FeatureClassInfo fci = getFeatureClassInfo(currentFeature);
int fciFeatureIDCol = fci.whatColumn(DcwRecordFile.ID_COLUMN_NAME);
// Don't know why currentFeature would be null, but reports from
// users demonstrate that it might be.
if ((fci == null) || (cat == null) || currentFeature == null) {
continue; // don't have enough info to proceed
// in an untiled coverage, we could probably work
// without the cat
}
// This looks like where most features get processed.
if (drawFeaturesFromThematicIndex(fci, warehouse, tables, ll1, ll2, dpplat, dpplon, currentFeature, featureType)) {
didSomething = true;
continue;
}
// couldn't use the tile_id thematic index, so just parse
// the whole file. This looks like the lessor case, trying to do
// something if the thematic index didn't work.
boolean needToFindOurselves = true;
TilingAdapter fciTilingAdapter = fci.getTilingAdapter();
if (fciTilingAdapter == null) {
// no way to find primitives
continue;
}
TileDirectory currentTile = null;
// There are going to be a variable number of columns.
// We're interested in the f_code, tile_id, and the
// primitive id (fci independent depending on type).
int oldTileID = -2; // -1 is "untiled" tile_id
// int faccIndex = fci.getFaccIndex()
// OK, now we are looking in the Feature class file.
try {
int getrow = 1;
for (List<Object> fcirow = new ArrayList<Object>(); fci.getRow(fcirow, getrow++);) {
if (Debug.debugging("vpfdetail")) {
Debug.output("CoverageTable new feature " + fcirow);
}
int tileID = fciTilingAdapter.getTileId(fcirow);
// String facc = (String)fcirow.get(faccIndex);
// With tileID, find the tile and figure out if it
// is needed.
if (tileID != oldTileID) {
tables.close();
if (Debug.debugging("vpf.tile")) {
Debug.output("CoverageTable.drawFeatures(): opening new tile ("
+ tileID + ")");
}
currentTile = (tileID == -1) ? new TileDirectory()
: cat.getTileWithID(tileID);
if (currentTile == null) {
Debug.error("VPFLayer|CoverageTable.drawFeatures: null tile from bogus ID ("
+ tileID + ") from " + fci.filename + ", skipping...");
continue;
}
if ((tileID == -1)
|| currentTile.inRegion(ll1.getLatitude(), ll2.getLatitude(), ll2.getLongitude(), ll1.getLongitude())) {
// We should only be in here once, I
// think, if the tile IDs are all stored
// in order. If the tile IDs are laid out
// in the FCI in jumbled order, only the
// first group of features will be
// gathered because the tile will appear
// cached.
String libraryname = "";
if (cat != null) {
libraryname = cat.libraryname;
}
if (!warehouse.needToFetchTileContents(libraryname, currentFeature, currentTile)) {
if (Debug.debugging("vpf")) {
Debug.output("CoverageTable: Loaded Cached List for "
+ currentFeature + " and " + currentTile.getPath());
}
continue;
}
if (Debug.debugging("vpf.tile")) {
Debug.output("CoverageTable: Drawing " + featureType
+ " features for " + currentTile);
}
tables.setTables(featureType, currentTile);
// Only need to do this once for a new
// fci...
if (needToFindOurselves) {
needToFindOurselves = false;
tables.findYourself(fci);
}
} else {
tables.close();
}
oldTileID = tileID;
}
// If currentTile == null, then the tile
// wasn't found, or it is outside the area of
// interest. The tables in the TableHolder
// (tables) will all be null, and the tables
// drawFeature will return false...
int primitiveID = fciTilingAdapter.getPrimId(fcirow);
int featureID = ((Number) fcirow.get(fciFeatureIDCol)).intValue();
if (tables.drawFeature(primitiveID, warehouse, ll1, ll2, dpplat, dpplon, currentFeature, featureID) != null) {
didSomething = true;
}
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("CoverageTable: Creating table: " + f.getClass() + " "
+ f.getMessage());
}
}
fci.close();
}
tables.close();
return didSomething;
}
/**
* This function gets the thematic index from the FeatureClassInfo object,
* and uses it to look up the tiles that contain the currentFeature. Then,
* that tile is checked to see if it is on the map. If it is, then that row
* in the thematic index is read to get the feature id numbers. The feature
* table is referenced for the feature ID number in the tile, and then the
* feature is drawn.
*
* @param fci the FeatureClassInfo (feature table)
* @param warehouse the VPFFeatureGraphicWarehouse to use to draw the
* graphics.
* @param ll1 the upper left corner of the map.
* @param ll2 the lower right corner of the map.
* @param dpplat degrees per pixel latitude direction.
* @param dpplon degrees per pixel longitude direction.
* @param currentFeature the feature string (roadl)
* @param featureType the CoverageTable letter representation of the feature
* type.
*/
protected boolean drawFeaturesFromThematicIndex(FeatureClassInfo fci,
VPFFeatureWarehouse warehouse,
TableHolder tables, LatLonPoint ll1,
LatLonPoint ll2, float dpplat, float dpplon,
String currentFeature, char featureType) {
if (!fci.initThematicIndex(tablepath)) {
return false;
}
List<Object> v = new ArrayList<Object>(); // hold fci row contents
try {
int primitiveIdColIndex = fci.getTilePrimitiveIdColIndex();
DcwThematicIndex thematicIndex = fci.getThematicIndex();
Object[] indexes = thematicIndex.getValueIndexes();
fci.reopen(1);
// We just know that these values are tile IDs.
for (int i = 0; i < indexes.length; i++) {
int tileID = VPFUtil.objectToInt(indexes[i]);
TileDirectory currentTile = cat.getTileWithID(tileID);
if (currentTile == null) {
Debug.error("VPFLayer|CoverageTable.drawFeatures: null tile from bogus ID ("
+ tileID + ") from " + fci.filename + ", skipping...");
continue;
}
if (currentTile.inRegion(ll1.getLatitude(), ll2.getLatitude(), ll2.getLongitude(), ll1.getLongitude())) {
tables.setTables(featureType, currentTile);
tables.findYourself(fci);
int[] featureID = thematicIndex.get(indexes[i]);
if (Debug.debugging("vpf.tile")) {
Debug.output("Drawing " + featureID.length + " " + featureType
+ " features for " + tileID + " " + currentTile);
}
String libraryname = cat.libraryname;
if (!warehouse.needToFetchTileContents(libraryname, currentFeature, currentTile)) {
if (Debug.debugging("vpf")) {
Debug.output("Loaded Cached List for " + currentFeature + " and "
+ currentTile.getPath());
}
continue;
}
for (int j = 0; j < featureID.length; j++) {
// featurePrimID is the unique ID of this feature. The
// primitiveID looks like it's the fac_id of the feature
// in the tile.
int featurePrimID = featureID[j];
if (!fci.getRow(v, featurePrimID)) {
// couldn't get row for some reason
continue;
}
int primitiveID = VPFUtil.objectToInt(v.get(primitiveIdColIndex));
// for attribute retrieval, we need the featurePrimID.
tables.drawFeature(primitiveID, warehouse, ll1, ll2, dpplat, dpplon, currentFeature, featurePrimID);
}
tables.close();
} else {
if (Debug.debugging("vpf.tile")) {
Debug.output("Skipping " + featureType + " features for " + tileID
+ ", not on map");
}
}
}
return true;
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("CoverageTable.DFFTI: Format Exception creating features: "
+ f.getClass() + " " + f.getMessage());
}
return false;
} finally {
fci.close();
}
}
/**
* This is a method specifically designed for the
* VPFAutoFeatureGraphicWarehouse, and the CoverageTable knows to check with
* the warehouse and use the PriorityHolders to fetch features.
*
* @param warehouse VPFAutoFeatureGraphicWarehouse
* @param ll1 upper left of coverage area
* @param ll2 lower right of coverage area
* @param dpplat degrees/pixel vertically
* @param dpplon degrees/pixel horizontally
* @param omgList The OMGraphicList to add OMGraphics, representing
* features.
* @throws FormatException if something goes wrong reading files, this
* exception will be thrown.
*/
public void getFeatures(VPFAutoFeatureGraphicWarehouse warehouse, LatLonPoint ll1,
LatLonPoint ll2, double dpplat, double dpplon, OMGraphicList omgList)
throws FormatException {
// The map of feature names versus their table information
Map<String, FeatureClassInfo> featureInfo = getFeatureClasses();
TableHolder tables = new TableHolder(this);
// Loop through the features, one by one.
for (Entry<String, FeatureClassInfo> entry : featureInfo.entrySet()) {
String featureName = entry.getKey();
FeatureClassInfo fci = entry.getValue();
if (fci == null) {
continue;
}
int fciFeatureIDCol = fci.whatColumn(DcwRecordFile.ID_COLUMN_NAME);
char featureType = whatFeatureType(warehouse, featureName);
fci.checkInit();
if (logger.isLoggable(Level.FINE)) {
logger.fine(" for " + featureName + ": " + fci.getDescription());
}
/**
* The features are made up of various FACC codes, that more
* precisely categorize what each feature is. We're going to look at
* each entry of this particular feature type, and use the
* PriorityHolders from the warehouse to determine when it gets
* drawn.
*/
int faccIndex = fci.getFaccIndex();
if (faccIndex < 0) {
continue;
}
int primitiveIDIndex = fci.getTilePrimitiveIdColIndex();
int tileIDIndex = fci.getTileIdIndex();
TileDirectory currentTile = null;
boolean needToFindOurselves = true;
TilingAdapter fciTilingAdapter = fci.getTilingAdapter();
if (fciTilingAdapter == null) {
// no way to find primitives
continue;
}
// There are going to be a variable number of columns.
// We're interested in the f_code, tile_id, and the
// primitive id (fci independent depending on type).
int oldTileID = -2; // -1 is "untiled" tile_id
int getrow = 1;
/**
* fcirow holds all the information for a particular feature. The
* fci lets you know what each column represents.
*/
for (List<Object> fcirow = new ArrayList<Object>(); fci.getRow(fcirow, getrow++);) {
String facc = (String) fcirow.get(faccIndex);
/**
* Get the list of FeaturePriorityHolders that correspond to
* this particlar facc. This list is just an organizational tool
* so we don't have to run through all of the FPHs.
*/
List<FeaturePriorityHolder> list = warehouse.faccLookup.get(facc);
if (list != null) {
boolean foundMatch = false;
for (FeaturePriorityHolder ph : list) {
/**
* Checking to see if the attributes for a particular
* feature match this particular FPH.
*/
if (ph.matches(facc, fci, fcirow)) {
foundMatch = true;
if (logger.isLoggable(Level.FINE)) {
logger.fine("+++ MATCH FOUND for " + facc + " tileid:"
+ fcirow.get(tileIDIndex) + ", primID:"
+ fcirow.get(primitiveIDIndex));
}
// Now we have the PriorityHolder that should
// receive the OMGraphic created for this feature.
// Use the CoverageTable to create it via the
// warehouse,
if (logger.isLoggable(Level.FINER)) {
logger.finer("CoverageTable new feature " + fcirow);
}
int tileID = fciTilingAdapter.getTileId(fcirow);
// With tileID, find the tile and figure out if it
// is needed.
if (tileID != oldTileID) {
tables.close();
if (logger.isLoggable(Level.FINER)) {
logger.finer("opening new tile (" + tileID + ")");
}
currentTile = (tileID == -1) ? new TileDirectory()
: cat.getTileWithID(tileID);
if (currentTile == null) {
if (logger.isLoggable(Level.FINE)) {
logger.warning("VPFLayer|CoverageTable.drawFeatures: null tile from bogus ID ("
+ tileID
+ ") from "
+ fci.filename
+ ", skipping...");
}
continue;
}
if ((tileID == -1)
|| currentTile.inRegion(ll1.getLatitude(), ll2.getLatitude(), ll2.getLongitude(), ll1.getLongitude())) {
// We should only be in here once, I
// think, if the tile IDs are all stored
// in order. If the tile IDs are laid out
// in the FCI in jumbled order, only the
// first group of features will be
// gathered because the tile will appear
// cached.
String libraryname = "";
if (cat != null) {
libraryname = cat.libraryname;
}
if (!warehouse.needToFetchTileContents(libraryname, featureName, currentTile)) {
if (Debug.debugging("vpf")) {
Debug.output("CoverageTable: Loaded Cached List for "
+ featureName + " and " + currentTile.getPath());
}
continue;
}
if (Debug.debugging("vpf.tile")) {
Debug.output("CoverageTable: Drawing " + featureType
+ " features for " + currentTile);
}
try {
tables.setTables(featureType, currentTile);
} catch (FormatException fe) {
System.out.println("missing tile, let's ignore that");
continue;
}
// Only need to do this once for a new
// fci...
if (needToFindOurselves) {
needToFindOurselves = false;
tables.findYourself(fci);
}
} else {
tables.close();
}
oldTileID = tileID;
}
// If currentTile == null, then the tile
// wasn't found, or it is outside the area of
// interest. The tables in the TableHolder
// (tables) will all be null, and the tables
// drawFeature will return false...
int primitiveID = fciTilingAdapter.getPrimId(fcirow);
int featureID = ((Number) fcirow.get(fciFeatureIDCol)).intValue();
OMGraphic omg = tables.drawFeature(primitiveID, warehouse, ll1, ll2, dpplat, dpplon, featureName, featureID);
if (omg != null) {
warehouse.handleInformationForOMGraphic(omg, fci, fcirow);
ph.add(omg);
if (logger.isLoggable(Level.FINEST)) {
StringBuffer pout = new StringBuffer();
for (Object obj : fcirow) {
pout.append(obj).append(',');
}
logger.finest(pout.toString());
}
}
} else {
// NOTE, this else statement is just for checking
// buoys.
// if (ph.facc.equals(facc)) {
// logger.info("something is getting blown off");
// ph.matches(facc, fci, fcirow);
// }
}
// And add it to the ph omgraphic list for
// retrieval a little bit later.
}
if (!foundMatch) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("--- NO MATCH FOUND for " + facc + ", type:" + featureType
+ ", tileid:" + fcirow.get(tileIDIndex) + ", primID:"
+ fcirow.get(primitiveIDIndex));
}
}
} else if (warehouse.debugFacc == null) {
if (logger.isLoggable(Level.FINE)) {
logger.info("didn't find facc list for: " + facc);
}
}
}
fci.close();
}
tables.close();
}
protected OMGraphic getOMGraphicForFeature() {
OMGraphic omg = null;
return omg;
}
/**
* Given a feature type name, figure out if the warehouse thinks it should
* *NOT* be drawn.
*
* @param warehouse the warehouse to build the graphics.
* @param featureName the VPF name of the feature (polbndl, for example).
* @return SKIP_FEATURETYPE if the feature should not be drawn.
*/
protected char whatFeatureType(VPFWarehouse warehouse, String featureName) {
// Test for the feature kind (edge, area, text, points) and
// don't continue if that type is not needed.
char featureType = SKIP_FEATURETYPE;
// Get the feature class for this feature type.
FeatureClassInfo fci = getFeatureClassInfo(featureName);
if (fci == null) {
return featureType;
}
char type = fci.getFeatureType();
if ((type == AREA_FEATURETYPE && warehouse.drawAreaFeatures())
|| (type == TEXT_FEATURETYPE && warehouse.drawTextFeatures())
|| (type == EDGE_FEATURETYPE && warehouse.drawEdgeFeatures())
|| (type == EPOINT_FEATURETYPE && warehouse.drawEPointFeatures())
|| (type == CPOINT_FEATURETYPE && warehouse.drawCPointFeatures())) {
featureType = type;
}
return featureType;
}
/**
* Feature Type Information read from VPF fca files.
*/
public static class FeatureClassRec {
/** the name of the feature class */
final public String feature_class;
/** the type of the feature */
final public char type;
/** a short text description */
final public String description;
/**
* Construct an instance of the class
*
* @param fclass the feature class name
* @param type the feature type
* @param desc the feature description
*/
public FeatureClassRec(String fclass, char type, String desc) {
feature_class = fclass;
this.type = type;
description = desc;
}
}
/**
* Returns a map from feature name to FeatureClassRec
*/
public Hashtable<String, FeatureClassRec> getFeatureTypeInfo() {
if (featureTypeInfo == null) {
featureTypeInfo = new Hashtable<String, FeatureClassRec>();
String path = getDataPath();
boolean addSlash = true;
// if (path.endsWith(File.separator)) {
if (path.endsWith("/") || path.endsWith(File.separator)) {
addSlash = false;
}
String fca = path + (addSlash ? "/" : "") + "fca";
if (!BinaryFile.exists(fca)) {
fca = fca + ".";
}
if (BinaryFile.exists(fca)) {
try {
DcwRecordFile fcadesc = new DcwRecordFile(fca);
int fclass = fcadesc.whatColumn("fclass");
int type = fcadesc.whatColumn("type");
int descr = fcadesc.whatColumn("descr");
for (ArrayList<Object> al = new ArrayList<Object>(fcadesc.getColumnCount()); fcadesc.parseRow(al);) {
String fname = ((String) al.get(fclass)).toLowerCase().intern();
char ftype = ((String) al.get(type)).charAt(0);
String fdesc = (String) al.get(descr);
FeatureClassRec fcr = new FeatureClassRec(fname, ftype, fdesc);
featureTypeInfo.put(fname, fcr);
}
fcadesc.close();
} catch (FormatException fe) {
// nevermind, skip it
}
}
}
return featureTypeInfo;
}
public static void main(String[] args) {
if (args.length != 5) {
System.out.println("This main() is just assorted test code.");
System.out.println("Usage: java classname librarypath coveragename");
System.out.println(" tablename attribute value");
System.out.println("Result: Prints the corresponding value in int.vdt");
} else {
CoverageTable ct = new CoverageTable(args[0], args[1]);
String desc = ct.getDescription(args[2], args[3], Integer.parseInt(args[4]));
System.out.println(desc);
}
}
}
/**
* The TableHolder is a utility class that manages the EdgeTable, TextTable and
* AreaTable that are needed by the CoverageTable to use the warehouse to create
* graphics.
*/
class TableHolder {
EdgeTable edg = null;
TextTable tft = null;
AreaTable aft = null;
NodeTable ent = null;
NodeTable cnt = null;
/** Used as a preallocated list to read feature tables. */
List<Object> primitiveVector = new ArrayList<Object>();
CoverageTable coverageTable;
/**
* Construct the TableHandler with the CoverageTable it is helping.
*/
protected TableHolder(CoverageTable ct) {
coverageTable = ct;
}
/**
* When drawing features (CoverageTable.drawFeatures()), sets up the
* TableHolder tables so that the right types are used.
*
* @param featureType from the CoverageTable, either AREA_FEATURETYPE,
* EDGE_FEATURETYPE or TEXT_FEATURETYPE.
* @param tile the tile directory that needs to be used when fetching
* graphics from the appropriate files.
*/
protected void setTables(char featureType, TileDirectory tile) throws FormatException {
if (featureType == CoverageTable.EDGE_FEATURETYPE) {
edg = new EdgeTable(coverageTable, tile);
}
if (featureType == CoverageTable.TEXT_FEATURETYPE) {
tft = new TextTable(coverageTable, tile);
}
if (featureType == CoverageTable.AREA_FEATURETYPE) {
aft = new AreaTable(coverageTable, null, tile);
edg = null;
}
if (featureType == CoverageTable.EPOINT_FEATURETYPE) {
ent = new NodeTable(coverageTable, tile, true);
}
if (featureType == CoverageTable.CPOINT_FEATURETYPE) {
cnt = new NodeTable(coverageTable, tile, false);
}
if (CoverageTable.logger.isLoggable(Level.FINE)) {
int activeTableCount = 0;
if (edg != null)
activeTableCount++;
if (tft != null)
activeTableCount++;
if (aft != null)
activeTableCount++;
if (ent != null)
activeTableCount++;
if (cnt != null)
activeTableCount++;
if (activeTableCount > 1) {
CoverageTable.logger.warning("TableHolder has more than one feature type");
}
}
}
/**
* Should be called once per FeatureClassInfo, after the tables have been
* set. Lets the tables figure out which columns to use as an index.
*/
protected void findYourself(FeatureClassInfo fci) {
if (aft != null) {
fci.findYourself(aft);
} else if (tft != null) {
fci.findYourself(tft);
} else if (edg != null) {
fci.findYourself(edg);
} else if (ent != null) {
fci.findYourself(ent);
}
}
/**
* Should be called once per feature, after the tables have been set
* (setTables()), and findYourself() has been called. The appropriate table
* will use the warehouse to create proper OMGraphic.
*/
protected OMGraphic drawFeature(int primitiveID, VPFFeatureWarehouse warehouse,
LatLonPoint ll1, LatLonPoint ll2, double dpplat, double dpplon,
String currentFeature, int featurePrimID)
throws FormatException {
if (aft != null || tft != null || edg != null || ent != null || cnt != null) {
// OK, now check to see what table is being
// used. if the tile is being reused, the
// table will be reused.
if ((aft != null) && aft.getRow(primitiveVector, primitiveID)) {
return aft.drawFeature(warehouse, dpplat, dpplon, ll1, ll2, primitiveVector, currentFeature, featurePrimID);
}
if ((tft != null) && tft.getRow(primitiveVector, primitiveID)) {
return tft.drawFeature(warehouse, dpplat, dpplon, ll1, ll2, primitiveVector, currentFeature, featurePrimID);
}
if ((ent != null) && ent.getRow(primitiveVector, primitiveID)) {
return ent.drawFeature(warehouse, dpplat, dpplon, ll1, ll2, primitiveVector, currentFeature, featurePrimID);
}
if ((cnt != null) && cnt.getRow(primitiveVector, primitiveID)) {
return cnt.drawFeature(warehouse, dpplat, dpplon, ll1, ll2, primitiveVector, currentFeature, featurePrimID);
}
if ((edg != null) && edg.getRow(primitiveVector, primitiveID)) {
return edg.drawFeature(warehouse, dpplat, dpplon, ll1, ll2, primitiveVector, currentFeature, featurePrimID);
}
}
return null;
}
/**
* Only call once per tile. It will parse all the needed data in the tile.
* Does not require setTables() or findYourself().
*/
protected void drawTile(TileDirectory tile, VPFGraphicWarehouse warehouse, LatLonPoint ll1,
LatLonPoint ll2, float dpplat, float dpplon) {
boolean drawedge = warehouse.drawEdgeFeatures();
boolean drawtext = warehouse.drawTextFeatures();
boolean drawarea = warehouse.drawAreaFeatures();
boolean drawepoint = warehouse.drawEPointFeatures();
boolean drawcpoint = warehouse.drawCPointFeatures();
close();
try {
if (drawedge || drawarea) {
edg = new EdgeTable(coverageTable, tile);
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("EdgeTable: " + f.getClass() + " " + f.getMessage());
}
}
try {
if (drawtext) {
tft = new TextTable(coverageTable, tile);
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("TextTable: " + f.getClass() + " " + f.getMessage());
}
}
try {
if (drawepoint) {
ent = new NodeTable(coverageTable, tile, true);
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("NodeTable: " + f.getClass() + " " + f.getMessage());
}
}
try {
if (drawcpoint) {
cnt = new NodeTable(coverageTable, tile, false);
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("NodeTable: " + f.getClass() + " " + f.getMessage());
}
}
try {
if (drawarea && (edg != null)) {
aft = new AreaTable(coverageTable, edg, tile);
}
} catch (FormatException f) {
if (Debug.debugging("vpf.FormatException")) {
Debug.output("AreaTable: " + f.getClass() + " " + f.getMessage());
}
}
if ((aft != null) && drawarea) {
for (int i = 0; i < coverageTable.areainfo.length; i++) {
coverageTable.areainfo[i].findYourself(aft);
}
aft.drawTile(warehouse, dpplat, dpplon, ll1, ll2);
}
if ((tft != null) && drawtext) {
for (int i = 0; i < coverageTable.textinfo.length; i++) {
coverageTable.textinfo[i].findYourself(tft);
}
tft.drawTile(warehouse, dpplat, dpplon, ll1, ll2);
}
if ((edg != null) && drawedge) {
for (int i = 0; i < coverageTable.lineinfo.length; i++) {
coverageTable.lineinfo[i].findYourself(edg);
}
edg.drawTile(warehouse, dpplat, dpplon, ll1, ll2);
}
if ((ent != null) && drawepoint) {
for (int i = 0; i < coverageTable.epointinfo.length; i++) {
coverageTable.epointinfo[i].findYourself(ent);
}
ent.drawTile(warehouse, dpplat, dpplon, ll1, ll2);
}
if ((cnt != null) && drawcpoint) {
for (int i = 0; i < coverageTable.cpointinfo.length; i++) {
coverageTable.cpointinfo[i].findYourself(cnt);
}
cnt.drawTile(warehouse, dpplat, dpplon, ll1, ll2);
}
// if (Debug.On && Debug.debugging("vpf.tile"))
// Debug.output(drawtd.toString() + " " + edgecount[0] +
// " polys with " + edgecount[1] +
// " points (cumulative)\n" +
// drawtd.toString() + " " + textcount[0] +
// " texts with " + textcount[1] +
// " points (cumulative)\n" +
// drawtd.toString() + " " + areacount[0] +
// " areas with " + areacount[1] +
// " points (cumulative)");
close();
}
/**
* Close any of these tables that may be in use.
*/
protected void close() {
if (Debug.debugging("vpf.tile")) {
Debug.output("CoverageTable closing tile tables");
}
if (edg != null) {
edg.close();
}
if (tft != null) {
tft.close();
}
if (aft != null) {
aft.close();
}
if (ent != null) {
ent.close();
}
if (cnt != null) {
cnt.close();
}
aft = null;
tft = null;
edg = null;
ent = null;
cnt = null;
}
}
/**
* A utility class used to map information from a VPF feature table to its
* associated value in an int.vdt file.
*/
class CoverageIntVdt {
/** the name of the table we are looking up (table is interned) */
final String table;
/**
* the name of the attribute we are looking up (attribute is interned)
*/
final String attribute;
/** the integer value we are looking up */
final int value;
/**
* Construct a new object
*
* @param t value for the table member
* @param a the value for the attribute member
* @param v the value for the value member
*/
public CoverageIntVdt(String t, String a, int v) {
table = t.toLowerCase().intern();
attribute = a.toLowerCase().intern();
value = v;
}
/**
* Override the equals method. Two CoverageIntVdts are equal if and only iff
* their respective table, attribute and value members are equal.
*/
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
final CoverageIntVdt civ = (CoverageIntVdt) o;
// we can use == rather than String.equals(String) since
// table and attribute are interned.
return ((table == civ.table) && (attribute == civ.attribute) && (value == civ.value));
}
/**
* Override hashcode. Compute a hashcode based on our member values, rather
* than our (base class) object identity.
*/
public int hashCode() {
return ((table.hashCode() ^ attribute.hashCode()) ^ value);
}
}
/**
* A utility class used to map information from a VPF feature table to its
* associated value in an char.vdt file.
*/
class CoverageCharVdt {
/** the name of the table we are looking up (table is interned) */
final String table;
/**
* the name of the attribute we are looking up (attribute is interned)
*/
final String attribute;
/** the character value we are looking up (value is interned) */
final String value;
/**
* Construct a new object
*
* @param t value for the table member
* @param a the value for the attribute member
* @param v the value for the value member
*/
public CoverageCharVdt(String t, String a, String v) {
table = t.toLowerCase().intern();
attribute = a.toLowerCase().intern();
value = v.intern();
}
/**
* Override the equals method. Two CoverageIntVdts are equal if and only iff
* their respective table, attribute and value members are equal.
*/
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (getClass() != o.getClass()) {
return false;
}
final CoverageCharVdt civ = (CoverageCharVdt) o;
// we can use == rather than String.equals(String) since
// table, attribute, and value are interned.
return ((table == civ.table) && (attribute == civ.attribute) && (value == civ.value));
}
/**
* Override hashcode. Compute a hashcode based on our member values, rather
* than our (base class) object identity.
*/
public int hashCode() {
return ((table.hashCode() ^ attribute.hashCode()) ^ value.hashCode());
}
}