// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/dataAccess/dted/DTEDFrameCache.java,v $ // $RCSfile: DTEDFrameCache.java,v $ // $Revision: 1.8 $ // $Date: 2007/02/26 16:41:51 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.dataAccess.dted; import java.awt.geom.Point2D; import java.util.Iterator; import java.util.Properties; import java.util.Vector; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.io.BinaryFile; import com.bbn.openmap.proj.EqualArc; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PropUtils; import com.bbn.openmap.util.cacheHandler.CacheHandler; import com.bbn.openmap.util.cacheHandler.CacheObject; /** * The DTEDFrameCache is an object that retrieves DTED paths, frames or * elevation values, given a latitude, longitude and dted level. It maintains a * collection of the frames it has already used for quicker access later. The * size of the cache is determined by startup settings. * <P> * * The DTEDFrameCache can be placed in the MapHandler, where other objects can * share it in order to all use the same DTED data. It can be configured with * properties: * <P> * * <pre> * * * frameCache.cacheSize=40 * frameCache.directoryHandlers=dteddir1 dteddir2 * frameCache.dteddir1.translator=com.bbn.openmap.dataAccess.dted.StandardDTEDNameTranslator * frameCache.dteddir1.path=/data/dted * frameCache.dteddir2.translator=com.bbn.openmap.dataAccess.dted.StandardDTEDNameTranslator * frameCache.dteddir2.path=/data/dted * * * </pre> * * A DTEDDirectoryHandler needs to be specified for each DTED directory you want * to use. If a translator isn't specified in the properties for a directory * handler, the StandardDTEDNameTranslator will be used. If you have DTED data * that doesn't conform to the naming conventions specified in the Military * Standard, you can use a different DTEDNameTranslator instead for your * particular directory handler. */ public class DTEDFrameCache extends CacheHandler implements PropertyConsumer { /** * The elevation value returned if there is no data at a lat/lon (-32767). */ public final static int NO_DATA = -32767; public static final String DTEDDirectoryHandlerProperty = "directoryHandlers"; public static final String DTEDFrameCacheSizeProperty = "cacheSize"; public int DEFAULT_CACHE_SIZE = 20; protected String propertyPrefix = null; protected Vector directories = new Vector(); protected int highestResLevel = 2; public DTEDFrameCache() { super(); } /** * Create the cache with paths to search for frames, and the maximum number * of frames to keep on hand. Assumes the paths given are for level 0 and 1 * data. * * @param max_size max number of frames to keep in the cache.. */ public DTEDFrameCache(int max_size) { super(max_size); } /** * Add a DTED DirectoryHandler to be used for the DTEDFrameCache. */ public void addDTEDDirectoryHandler(DTEDDirectoryHandler handler) { directories.add(handler); } /** * Remove a DTED DirectoryHandler from the list used for the DTEDFrameCache. */ public void removeDTEDDirectoryHandler(DTEDDirectoryHandler handler) { directories.remove(handler); } /** * Get the Vector of DTEDDirectoryHandlers used by the DTEDFrameCache. */ public Vector getDTEDDirectoryHandlers() { return directories; } /** * Set the Vector of DTEDDirectoryHandlers used by the DTEDFrameCache. You * might want to use this to set the order of directories that are searched * for a DTED frame. */ public void setDTEDDirectoryHandlers(Vector handlers) { directories = handlers; } /** * A utility to find the path to a dted file, given a lat, lon and a dted * level. Assumes that paths have been given to the cache. Lat/lons in * decimal degrees. * * @param lat latitude of point * @param lon longitude of point * @param level the dted level wanted (0, 1) * @return complete path to file with lat/lon. */ public String findFileName(double lat, double lon, int level) { if (directories != null) { for (Iterator it = directories.iterator(); it.hasNext();) { DTEDDirectoryHandler ddh = (DTEDDirectoryHandler) it.next(); DTEDNameTranslator dnt = ddh.getTranslator(); dnt.set(lat, lon, level); String dtedFileName = dnt.getName(); if (Debug.debugging("dtedfile")) { Debug.output("DTEDFrameCache translator returns " + dtedFileName + " for " + lat + ", " + lon + ", level " + level); } if (BinaryFile.exists(dtedFileName)) { return dtedFileName; } } } return null; } /** * Return The DTED Frame, Given A Lat, Lon And DTED Level. * * @param lat latitude of point * @param lon longitude of point * @param level the dted level wanted (0, 1, 2) * @return DTED frame. */ public DTEDFrame get(double lat, double lon, int level) { String name = findFileName(lat, lon, level); if (name != null) { if (Debug.debugging("dtedfile")) { Debug.output("DTEDFrameCache: returning " + name + " for " + lat + ", " + lon + ", level " + level); } return (DTEDFrame) get(name); } else { if (Debug.debugging("dtedfile")) { Debug.output("DTEDFrameCache: couldn't find frame for " + lat + ", " + lon + ", level " + level); } } return null; } /** * A private class that makes sure that cached frames get disposed properly. */ private static class DTEDCacheObject extends CacheObject { /** * Construct a DTEDCacheObject, just calls superclass constructor * * @param id passed to superclass * @param obj passed to superclass */ public DTEDCacheObject(String id, DTEDFrame obj) { super(id, obj); } /** * Calls dispose() on the contained frame, to make it eligible for * garbage collection. */ protected void finalize() { ((DTEDFrame) obj).dispose(); } } /** * Load a dted frame into the cache, based on the path of the frame as a * key. * * @param key complete path to the frame, String. * @return DTED frame, hidden as a CacheObject. */ public CacheObject load(Object key) { if (key != null) { String dtedFramePath = key.toString(); DTEDFrame frame = new DTEDFrame(dtedFramePath, true); if (frame.frame_is_valid) { return new DTEDCacheObject(dtedFramePath, frame); } } return null; } /** * This version of resizeCache is for screen size changes, where the number * of frames kept on hand in the cache must change, but the images * themselves don't have to because the pixel/posting spacing hasn't changed * in the projection. The frames already in the cache are re-added to the * new cache, if the cache size is increasing. If the cache size is * shrinking, then as many as will fit are added to the new cache. * * @param max_size the new size of the cache. */ public void resizeCache(int max_size) { CacheObject[] oldObjs = objs; if (max_size != objs.length && max_size > 0) { objs = new CacheObject[max_size]; } for (int i = 0; i < objs.length; i++) { if (i >= oldObjs.length) { break; } DTEDCacheObject dco = (DTEDCacheObject) oldObjs[i]; if (dco == null) { // We load from the front to the back 0 -> length - 1; // Once you hit a null, the rest should be null, too. objs[i] = null; continue; } DTEDFrame frame = (DTEDFrame) (dco.obj); dco.cachedTime = 0; if (frame == null) { Debug.output("DTEDFrameCache: No Frame for key!"); continue; } if (oldObjs == objs) { continue; } if (i < oldObjs.length) { objs[i] = oldObjs[i]; } else { objs[i] = null; } } oldObjs = null; } public int getHighestResLevel() { return highestResLevel; } public void setHighestResLevel(int highestResLevel) { this.highestResLevel = highestResLevel; } /** * Return the elevation of a lat/lon point, in meters. Will look for frames * starting at the highest resolution specified in this DTEDFrameCache, and * work up to level 0, searching for a frame to provide an answer. Will * return NO_DATA if a frame is not found, or if there is no data in the * frame file that is found. The default highest resolution DTED level is 2. * * @return elevation in meters. * @param lat in decimal degrees. * @param lon in decimal degrees. */ public int getElevation(float lat, float lon) { for (int i = /* dted level */highestResLevel; i >= /* dted level */0; i--) { String dtedFileName = findFileName((double) lat, (double) lon, i); if (dtedFileName != null) { DTEDFrame frame = (DTEDFrame) get(dtedFileName); if (frame != null) { return (int) frame.elevationAt(lat, lon); } } } return NO_DATA; } /** * Return the elevation of a lat/lon point, in meters. * * @return elevation in meters. * @param lat in decimal degrees. * @param lon in decimal degrees. * @param level the dted level. */ public int getElevation(float lat, float lon, int level) { String dtedFileName = findFileName((double) lat, (double) lon, level); if (dtedFileName != null) { DTEDFrame frame = (DTEDFrame) get(dtedFileName); if (frame != null) { return (int) frame.elevationAt(lat, lon); } } return NO_DATA; } /** * Return the two-dimensional matrix of elevation posts (heights) * representing coverage of a given geographical rectangle. The matrix * represents coverage in an Equal Arc projection, and that's why the * rectangle is defined by the projection parameters. * * @param proj the projection describing the wanted area * @param dtedLevel the DTED level (0, 1, 2) to be used, which describes the * geographicsal spacing between the posts. * @return array of elevations, in meters. Spacing depends on the DTED * level. */ public short[][] getElevations(EqualArc proj, int dtedLevel) { Point2D ul = proj.getUpperLeft(); Point2D lr = proj.getLowerRight(); return getElevations((float) ul.getY(), (float) ul.getX(), (float) lr.getY(), (float) lr.getX(), dtedLevel); } /** * Return the two-dimensional matrix of elevation posts (heights) * representing coverage of a given geographical rectangle. The matrix * represents coverage in an Equal Arc projection. Doesn't handle * projections which cross the dateline - You must handle that yourself by * making two inquiries. * * @param ullat upper latitude, in decimal degrees * @param ullon left longitude, in decimal degrees * @param lrlat lower latitude, in decimal degrees * @param lrlon right longitude, in decimal degrees * @param dtedLevel the DTED level (0, 1, 2) to be used, which describes the * geographicsal spacing between the posts. */ public short[][] getElevations(float ullat, float ullon, float lrlat, float lrlon, int dtedLevel) { return getElevations(ullat, ullon, lrlat, lrlon, dtedLevel, null); } /** * Return the two-dimensional matrix of elevation posts (heights) * representing coverage of a given geographical rectangle. The matrix * represents coverage in an Equal Arc projection. Doesn't handle * projections which cross the dateline - You must handle that yourself by * making two inquiries. * <P> * This method is slightly different that the one above, because it includes * a input variable DTEDFrame. There is an inherent problem in the algorithm * if some of the DTED frames are missing. It's too difficult to calculate * the size of the return array if you don't know that any frames are * available. So, you should always use the method above, which calls this * method with a null refFrame. If some of the DTED frames are missing, then * this method is called recursively, with a frame to use for calculating * post spacings at the right time. * * @param ullat upper latitude, in decimal degrees * @param ullon left longitude, in decimal degrees * @param lrlat lower latitude, in decimal degrees * @param lrlon right longitude, in decimal degrees * @param dtedLevel the DTED level (0, 1, 2) to be used, which describes the * geographicsal spacing between the posts. * @param refFrame DTEDFrame used to calculate measurements. * @return array of elevations, in meters. Spacing depends on the DTED * level. */ protected short[][] getElevations(float ullat, float ullon, float lrlat, float lrlon, int dtedLevel, DTEDFrame refFrame) { float upper = ullat; float lower = lrlat; float right = lrlon; float left = ullon; // Since matrix indexes depend on these being in the right // order, we'll double check and flip values, just to make // sure lower is lower, and higher is higher. if (ullon > lrlon) { if (ullon > 0 && lrlon < 0) { Debug.error("DTEDFrameCache: getElevations: Stradling dateline not handled!"); return null; } right = ullon; left = lrlon; } if (lrlat > ullat) { upper = lrlat; lower = ullat; } // These are the limits of the lat/lons per frame searched float upperlat = 0; float upperlon = 0; float lowerlat = 0; float lowerlon = 0; int xSize = (int) (Math.ceil(right) - Math.floor(left)); int ySize = (int) (Math.ceil(upper) - Math.floor(lower)); // System.out.println("Going with size = " + xSize + "x" + // ySize); int[] xLengths = new int[xSize]; int[] yLengths = new int[ySize]; short[][][][] es = new short[xSize][ySize][][]; int x, y; DTEDFrame frame = null; boolean needCalc = false; // Let's march through the frames, bottom to top, left to // right. for (x = 0; x < xSize; x++) { if (x == 0) lowerlon = left; else lowerlon = (float) Math.floor(left) + (float) x; if (x == xSize - 1) upperlon = right; else upperlon = (float) Math.floor(left) + (float) (x + 1); for (y = 0; y < ySize; y++) { if (y == 0) lowerlat = lower; else lowerlat = (float) Math.floor(lower) + (float) y; if (y == ySize - 1) upperlat = upper; else upperlat = (float) Math.floor(lower) + (float) (y + 1); DTEDFrame thisFrame = get(lowerlat, lowerlon, dtedLevel); if (thisFrame != null) { // System.out.println("Getting elev for " + // upperlat + ", " + // lowerlon + ", " + // lowerlat+ ", " + upperlon); es[x][y] = thisFrame.getElevations(upperlat, lowerlon, lowerlat, upperlon); xLengths[x] = es[x][y].length; yLengths[y] = es[x][y][0].length; frame = thisFrame; } else { if (refFrame != null) { Debug.output("DTEDFrameCache: Missing frames, going to use reference frame"); // calculate these lengths, since the refFrame // was set... int[] indexes = refFrame.getIndexesFromLatLons(upperlat, lowerlon, lowerlat, upperlon); xLengths[x] = indexes[2] - indexes[0] + 1; yLengths[y] = indexes[3] - indexes[1] + 1; } else { if (frame != null) { // Well, we have a frame to do // calculations on, and we know we need // to do at least one calculation, so // might as well go and do this right... return getElevations(ullat, ullon, lrlat, lrlon, dtedLevel, frame); } else { needCalc = true; } } } } } // refFrame == null, and all the empty frames were found // before the good ones... if (needCalc == true && frame != null) return getElevations(ullat, ullon, lrlat, lrlon, dtedLevel, frame); int xLength = 0; int yLength = 0; // Need to figure out how big the returned matrix is! This // only works if all the frames come back.... for (x = 0; x < xLengths.length; x++) xLength += xLengths[x]; for (y = 0; y < yLengths.length; y++) yLength += yLengths[y]; // System.out.println("Creating a matrix: " + xLength + "x" + // yLength); short[][] matrix = new short[xLength][yLength]; // Now copy all the little matrixes into the big matrix int xspacer = 0; // Through each little matrix in the x direction for (x = 0; x < es.length; x++) { int yspacer = 0; // Through each little matrix in the y direction for (y = 0; y < es[x].length; y++) { // Make sure the frame exists and is found... if (es[x][y] != null) { // Through each lon row in each little matrix for (int i = 0; i < es[x][y].length; i++) { System.arraycopy(es[x][y][i], 0, matrix[i + xspacer], yspacer, es[x][y][i].length); } // On the last one lon column, increase the spacer // for the // next little matrix above this one. yspacer += yLengths[y]; } else yspacer += xLengths[y]; } // On the last little matrix in the column, increase the // xspacer for the little matrixes in the next column. xspacer += xLengths[x]; } return matrix; } /** * PropertyConsumer method. */ public void setPropertyPrefix(String prefix) { propertyPrefix = prefix; } /** * PropertyConsumer method. */ public String getPropertyPrefix() { return propertyPrefix; } /** * PropertyConsumer method. */ public void setProperties(Properties props) { setProperties(null, props); } /** * PropertyConsumer method. */ public void setProperties(String prefix, Properties props) { setPropertyPrefix(prefix); prefix = PropUtils.getScopedPropertyPrefix(this); // Space-separated list of marker names for different // DTEDDirectoryHandlers Vector directoryHandlerList = PropUtils.parseSpacedMarkers(props.getProperty(prefix + DTEDDirectoryHandlerProperty)); for (Iterator it = directoryHandlerList.iterator(); it.hasNext();) { String handlerPrefix = (String) it.next(); DTEDDirectoryHandler handler = new DTEDDirectoryHandler(); handler.setProperties(prefix + handlerPrefix, props); addDTEDDirectoryHandler(handler); } resetCache(PropUtils.intFromProperties(props, prefix + DTEDFrameCacheSizeProperty, DEFAULT_CACHE_SIZE)); } /** * PropertyConsumer method. */ public Properties getProperties(Properties props) { if (props == null) { props = new Properties(); } String prefix = PropUtils.getScopedPropertyPrefix(this); props.put(prefix + DTEDFrameCacheSizeProperty, Integer.toString(getCacheSize())); // Directory handler properties... if (directories != null) { StringBuffer dhPrefixes = new StringBuffer(); for (Iterator it = directories.iterator(); it.hasNext();) { DTEDDirectoryHandler ddh = (DTEDDirectoryHandler) it.next(); String dhPrefix = ddh.getPropertyPrefix(); if (dhPrefix != null) { int index = dhPrefix.indexOf(prefix); if (index != -1) { dhPrefixes.append(dhPrefix.substring(index + prefix.length())).append(" "); } ddh.getProperties(props); } } props.put(prefix + DTEDDirectoryHandlerProperty, dhPrefixes.toString()); } return props; } /** * PropertyConsumer method. */ public Properties getPropertyInfo(Properties props) { if (props == null) { props = new Properties(); } props.put(DTEDFrameCacheSizeProperty, "Size of the frame cache"); // Not sure how to handle setting up a DTEDDirectoryHandler // yet. return props; } public static void main(String[] args) { Debug.init(); if (args.length < 1) { Debug.output("DTEDFrameCache: Need a path/filename"); System.exit(0); } Debug.output("DTEDFrameCache: " + args[0]); DTEDFrameCache dfc = new DTEDFrameCache(10); // 35.965065 -121.198715 // 35.998 36.002 lon -121.002 -120.998 float ullat = 37.002f; float ullon = -121.002f; float lrlat = 35.998f; float lrlon = -119.998f; // System.out.println("Getting elevations for " + // ullat + ", " + ullon + ", " + // lrlat + ", " + lrlon); short[][] e = dfc.getElevations(ullat, ullon, lrlat, lrlon, 0); if (e != null) { for (int i = e[0].length - 1; i >= 0; i--) { int col = 0; System.out.print("r" + i + "-"); for (int j = 0; j < e.length; j++) { System.out.print(e[j][i] + " "); col++; } System.out.println(" - " + col); } } } }