// ********************************************************************** // // <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/dted/DTEDFrameCache.java,v $ // $RCSfile: DTEDFrameCache.java,v $ // $Revision: 1.9 $ // $Date: 2005/12/09 21:09:06 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.dted; import java.awt.geom.Point2D; import java.util.Properties; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.dataAccess.dted.DTEDFrameUtil; 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 user-determined, and the cache also relies on user * provided paths to the dted directory, which is the top level directory in a * dted file structure. The paths are provided in a String array, so you can * have data in different places on a system, including a CDROM drive. */ public class DTEDFrameCache extends CacheHandler implements PropertyConsumer { /** The elevation value returned if there is no data at a lat/lon. */ public final static int NO_DATA = -500; /** * The maximum DTED level to check for data, given a lat/lon and a path to a * dted data directory. */ public final static int MAX_DTED_LEVEL = 1; /** An array of strings representing data directory paths. */ protected String[] dtedDirPaths; /** * Number of subframes used by each frame. Calculated by the * DTEDCacheHandler when it is given a projection. */ protected int numXSubframes; // per frame, at the current scale /** * Number of subframes used by each frame. Calculated by the * DTEDCacheHandler when it is given a projection. */ protected int numYSubframes; public static final String DTEDPathsProperty = "paths"; public static final String DTEDFrameCacheSizeProperty = "cacheSize"; protected String propertyPrefix = null; 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 dtedPaths path to the level 0 and level 1 dted directories * @param max_size max number of frames to keep in the cache.. */ public DTEDFrameCache(String[] dtedPaths, int max_size) { super(max_size); dtedDirPaths = dtedPaths; } /** * Set the data paths. * * @param paths paths to the dted level 0 and 1 directories. */ public void setDtedDirPaths(String[] paths) { dtedDirPaths = paths; } /** * 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. * * @return complete path to file with lat/lon. * @param lat latitude of point * @param lon longitude of point * @param level the dted level wanted (0, 1, 2, 3) */ public String findFileName(double lat, double lon, int level) { String lonString = DTEDFrameUtil.lonToFileString((float) lon); String latString = DTEDFrameUtil.latToFileString((float) lat, level); String partialFile = "/" + lonString + "/" + latString; String ret = findFileName(dtedDirPaths, partialFile); return ret; } /** * Method to check the searchPaths for a file. * * @param searchPaths an array of dted root directories * @param partialFile the relative pathname of a dted frame file from the * dted root. * @return the name of the file, or null if not found. */ protected String findFileName(String[] searchPaths, String partialFile) { if (searchPaths == null || searchPaths.length == 0) { return null; } for (int i = 0; i < searchPaths.length; i++) { String dtedFileName = searchPaths[i] + partialFile; if (BinaryFile.exists(dtedFileName)) { return dtedFileName; } } return null; } /** * Return The Dted Frame, Given A Lat, Lon And Dted Level. * * @return Dted frame. * @param lat latitude of point * @param lon longitude of point * @param level the dted level wanted (0, 1, 2) */ public DTEDSubframedFrame get(double lat, double lon, int level) { String name = findFileName(lat, lon, level); if (name != null) { return (DTEDSubframedFrame) get(name); } 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, DTEDSubframedFrame obj) { super(id, obj); } /** * Calls dispose() on the contained frame, to make it eligible for * garbage collection. */ protected void finalize() { ((DTEDSubframedFrame) 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, as a string. * @return DTED frame, hidden in a CacheObject. */ public CacheObject load(Object key) { if (key != null) { String dtedFramePath = key.toString(); // If it's a DTED level 0 frame, read it all in, // otherwise, read just what you need. DTEDSubframedFrame frame = new DTEDSubframedFrame(dtedFramePath, dtedFramePath.endsWith("dt0")); frame.initSubframes(numXSubframes, numYSubframes); if (frame.frame_is_valid) { return new DTEDCacheObject(dtedFramePath, frame); } } return null; } /** * This version of resizeCache is for projection changes, where the * post/pixel spacing of the images has changed, and the images need to be * rebuilt. The cache size will change based on scale, because more frames * are needed for smaller scales. If the number of subframes in either * direction is zero, then the resize becomes non-destructive, which means * that the frames will not delete their subframes. If the scale of the map * changes, then the frame subframe sizes need to be recalculated, and a * destructive resizing is necessary. * * @param max_size the maximum number of frames in the cache. * @param num_x_subframes the number of horizontal subframes in each frame. * @param num_y_subframes the number of vertical subframes in each frame. */ public void resizeCache(int max_size, int num_x_subframes, int num_y_subframes) { boolean destructive = false; if (num_x_subframes > 0 && num_y_subframes > 0) { numXSubframes = num_x_subframes; numYSubframes = num_y_subframes; destructive = true; Debug.message("dted", "DTEDFrameCache: destructive resizing"); } else { Debug.message("dted", "DTEDFrameCache: passive resizing"); } 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; } DTEDSubframedFrame frame = (DTEDSubframedFrame) (dco.obj); dco.cachedTime = 0; if (frame == null) { Debug.output("DTEDFrameCache: No Frame for key!"); continue; } if (destructive) { frame.initSubframes(num_x_subframes, num_y_subframes); } else { // If it's not destructive, and the size didn't // change, then just continue through the loop // resetting the cachedTime for the CacheObjects. if (oldObjs == objs) { continue; } } if (i < oldObjs.length) { objs[i] = oldObjs[i]; } else { objs[i] = null; } } oldObjs = 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) { resizeCache(max_size, 0, 0); } /** * Return the elevation of a lat/lon point, in meters. * * @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 */2; i >= /* dted level */0; i--) { DTEDSubframedFrame frame = null; String dtedFileName = findFileName((double) lat, (double) lon, i); if (dtedFileName != null) frame = (DTEDSubframedFrame) 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, DTEDSubframedFrame 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; DTEDSubframedFrame 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); DTEDSubframedFrame 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); String[] paths = PropUtils.initPathsFromProperties(props, prefix + DTEDPathsProperty); setDtedDirPaths(paths); resetCache((int) PropUtils.intFromProperties(props, prefix + DTEDFrameCacheSizeProperty, DTEDCacheHandler.FRAME_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())); // find out paths... String[] p; String prop = null; StringBuffer pathString = new StringBuffer(); if (dtedDirPaths != null && dtedDirPaths.length > 0) { for (String path : dtedDirPaths) { if (pathString.length() > 0) { pathString.append(';'); } pathString.append(path); } } props.put(prefix + prop, pathString.toString()); return props; } /** * PropertyConsumer method. */ public Properties getPropertyInfo(Properties props) { if (props == null) { props = new Properties(); } props.put(DTEDPathsProperty, "Paths to the DTED directories"); props.put(DTEDFrameCacheSizeProperty, "Size of the frame cache"); 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(args, 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); } } } }