//********************************************************************** // //<copyright> // //BBN Technologies //10 Moulton Street //Cambridge, MA 02138 //(617) 873-8000 // //Copyright (C) BBNT Solutions LLC. All rights reserved. // //</copyright> //********************************************************************** // //$Source: ///cvs/darwars/ambush/aar/src/com/bbn/ambush/mission/MissionHandler.java,v //$ //$RCSfile: MissionHandler.java,v $ //$Revision: 1.10 $ //$Date: 2004/10/21 20:08:31 $ //$Author: dietrick $ // //********************************************************************** package com.bbn.openmap.dataAccess.mapTile; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.logging.Level; import javax.swing.ImageIcon; import com.bbn.openmap.Environment; import com.bbn.openmap.I18n; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.FileUtils; import com.bbn.openmap.util.PropUtils; import com.bbn.openmap.util.cacheHandler.CacheObject; /** * The ServerMapTileFactory is an extension to the StandardMapTileFactory that * can go to a http server to retrieve image tiles. You provide it with a root * URL that points to the parent directory of the tiles, and then this component * will add on the zoom/x/y.extension to that directory path to make the call * for a specific tile. Please make sure you have the permission of the server's * owner before hammering away at retrieving tiles from it. * * This component can be configured using properties: * <p> * * <pre> * # Inherited from StandardMapTileFactory * rootDir=the URL to the parent directory of the tiles on a server. The factory will construct specific file paths that are appended to this value. * fileExt=the file extension to append to the tile names, should have a period. * cacheSize=the number of mapTiles the factory should hold on to. The default is 100. * * # Additional properties * localCacheRootDir=if specified, the factory will store tiles locally at this root directory. This directory is checked before going to the server, too. * </pre> * * @author dietrick */ public class ServerMapTileFactory extends StandardMapTileFactory implements MapTileFactory, PropertyConsumer { public final static String LOCAL_CACHE_ROOT_DIR_PROPERTY = "localCacheRootDir"; protected String localCacheDir = null; public ServerMapTileFactory() { this(null); } public ServerMapTileFactory(String rootDir) { this.rootDir = rootDir; this.fileExt = ".png"; verbose = logger.isLoggable(Level.FINE); } /** * An auxiliary call to retrieve something from the cache, modified to allow * load method to do some projection calculations to initialize tile * parameters. If the object is not found in the cache, null is returned. */ public Object getFromCache(Object key, int x, int y, int zoomLevel) { String localLoc = null; if (localCacheDir != null && zoomLevelInfo != null) { localLoc = buildLocalFilePath(x, y, zoomLevel, fileExt); /** * If a local cache is defined, then the cache will always use the * string for the local file as the key. */ CacheObject ret = searchCache(localLoc); if (ret != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("found tile (" + x + ", " + y + ") in cache"); } return ret.obj; } /** * Return null if the localized version isn't found in cache when * local version is defined. */ return null; } // Assuming that the localCacheDir is not defined, so the cache objects // will be using the server location as key CacheObject ret = searchCache(key); if (ret != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("found tile (" + x + ", " + y + ") in cache"); } return ret.obj; } return null; } /** * Checks the local directory first for a locally cached version of the tile * before going off to the server. If a local directory is listed as a * cache, any retrieved files will be stored there for future use. We are * using the local name of the file as the cache key for all tiles for * consistency - all tiles are looked up with local cache locations. */ public CacheObject load(Object key, int x, int y, int zoomLevel, Projection proj) { if (key instanceof String) { if (verbose) { logger.fine("fetching file for cache: " + key); } byte[] imageBytes = null; CacheObject localVersion = super.load(key, x, y, zoomLevel, proj); if (localVersion != null) { logger.fine("found version of tile in local cache: " + key); return localVersion; } // build file path here uses rootDir, which is the URL. String imagePath = buildFilePath(x, y, zoomLevel, fileExt); imageBytes = getImageBytes(imagePath, (String) key); if (imageBytes != null && imageBytes.length > 0) { // image found ImageIcon ii = new ImageIcon(imageBytes); try { BufferedImage rasterImage = preprocessImage(ii.getImage(), ii.getIconWidth(), ii.getIconHeight()); OMGraphic raster = createOMGraphicFromBufferedImage(rasterImage, x, y, zoomLevel, proj); /* * Again, create a CacheObject based on the local name if * the local dir is defined. */ if (raster != null) { return new CacheObject(key, raster); } } catch (InterruptedException ie) { if (logger.isLoggable(Level.FINE)) { logger.fine("factory interrupted fetching " + imagePath); } } } /* * At this point, nothing was found for this location, so it's an * empty tile. */ return getEmptyTile(key, x, y, zoomLevel, proj); } return null; } /** * Tries to get the image bytes from imagePath URL. If image found, will * write it locally to localFilePath for caching. * * @param imagePath the source URL image path. * @param localFilePath the caching local file path * @return byte[] of image */ public byte[] getImageBytes(String imagePath, String localFilePath) { byte[] imageBytes = null; try { java.net.URL url = new java.net.URL(imagePath); java.net.URLConnection urlc = url.openConnection(); if (logger.isLoggable(Level.FINER)) { logger.finer("url content type: " + urlc.getContentType()); } if (urlc == null || urlc.getContentType() == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("unable to connect to (tile might be unavailable): " + imagePath); } // text } else if (urlc.getContentType().startsWith("text")) { java.io.BufferedReader bin = new java.io.BufferedReader(new java.io.InputStreamReader(urlc.getInputStream())); String st; StringBuffer message = new StringBuffer(); while ((st = bin.readLine()) != null) { message.append(st); } // Debug.error(message.toString()); // How about we toss the message out to the user // instead? logger.fine(message.toString()); // image } else if (urlc.getContentType().startsWith("image")) { InputStream in = urlc.getInputStream(); // ------- Testing without this ByteArrayOutputStream out = new ByteArrayOutputStream(); int buflen = 2048; // 2k blocks byte buf[] = new byte[buflen]; int len = -1; while ((len = in.read(buf, 0, buflen)) != -1) { out.write(buf, 0, len); } out.flush(); out.close(); imageBytes = out.toByteArray(); if (localFilePath != null) { File localFile = new File(localFilePath); File parentDir = localFile.getParentFile(); parentDir.mkdirs(); FileOutputStream fos = new FileOutputStream(localFile); fos.write(imageBytes); fos.flush(); fos.close(); } } // end if image } catch (java.net.MalformedURLException murle) { logger.warning("ServerMapTileFactory: URL \"" + imagePath + "\" is malformed."); } catch (java.io.IOException ioe) { logger.fine("Couldn't connect to " + imagePath + ", connection problem"); } return imageBytes; } /** * Acts the same as the buildFilePath method, but works for a local * directory specified in the properties. * * @param x tile coordinate * @param y tile coordinate * @param z zoom level * @param fileExt file extension for image tiles. * @return new path for tile file */ public String buildLocalFilePath(int x, int y, int z, String fileExt) { if (localTilePathBuilder == null) { localTilePathBuilder = new TilePathBuilder(localCacheDir); } return localTilePathBuilder.buildTilePath(x, y, z, fileExt); } private TilePathBuilder localTilePathBuilder = null; /** * Creates a unique cache key for this tile based on zoom, x, y. This method * was created so the ServerMapTileFactory could override it and use local * cache names for keys if a local cache was being used. * * @param x tile coord. * @param y tile coord. * @param z zoomLevel. * @param fileExt file extension. * @return String used in cache. */ protected String buildCacheKey(int x, int y, int z, String fileExt) { if (localCacheDir != null) { return buildLocalFilePath(x, y, z, fileExt); } return super.buildCacheKey(x, y, z, fileExt); } public Properties getProperties(Properties getList) { getList = super.getProperties(getList); getList.put(prefix + LOCAL_CACHE_ROOT_DIR_PROPERTY, PropUtils.unnull(localCacheDir)); return getList; } public Properties getPropertyInfo(Properties list) { list = super.getPropertyInfo(list); I18n i18n = Environment.getI18n(); PropUtils.setI18NPropertyInfo(i18n, list, com.bbn.openmap.dataAccess.mapTile.StandardMapTileFactory.class, LOCAL_CACHE_ROOT_DIR_PROPERTY, "Local Cache Tile Directory", "Root directory containing image tiles retrieved from image server.", "com.bbn.openmap.util.propertyEditor.DirectoryPropertyEditor"); return list; } public void setProperties(String prefix, Properties setList) { super.setProperties(prefix, setList); prefix = PropUtils.getScopedPropertyPrefix(prefix); localCacheDir = setList.getProperty(prefix + LOCAL_CACHE_ROOT_DIR_PROPERTY, localCacheDir); } /** * Tell the factory to dump the cache. For the ServerMapTileFactory, this * also includes the local file cache dir. */ public void reset() { super.reset(); if (localCacheDir != null) { File localCacheDirFile = new File(localCacheDir); if (localCacheDirFile.exists()) { try { FileUtils.deleteFile(localCacheDirFile); } catch (IOException e) { logger.fine("There's a problem deleting local cache directory: " + e.getMessage()); } } } } }