// ********************************************************************** // // <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/image/ImageServer.java,v $ // $RCSfile: ImageServer.java,v $ // $Revision: 1.14 $ // $Date: 2007/04/17 20:23:44 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.image; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import com.bbn.openmap.Environment; import com.bbn.openmap.Layer; import com.bbn.openmap.MapBean; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.plugin.PlugIn; import com.bbn.openmap.plugin.PlugInLayer; import com.bbn.openmap.proj.GeoProj; import com.bbn.openmap.proj.Mercator; import com.bbn.openmap.proj.Proj; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.ProjectionFactory; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.ComponentFactory; import com.bbn.openmap.util.PropUtils; /** * The image server is the class you want to deal with when creating images. It * takes a properties file and creates images based on those properties. It also * has this queuing thing going so that requests can stack up while the image * generator is working on requests, and it will notify the requestor when the * image is ready. * <P> * * The ImageServer generally has the layers on the map predefined at * construction, although you can change the layers that it has. When setting * the layer array, do not use the same layer in two different slots of the * array - it may not give you the expected map, and may mess around with the * timing issues that the ImageGenerator takes care of. If you want to reorder * the layers, do so before adding your request to the ImageServer. * Additionally, each request has the option of not using certain layers in the * ImageServer layer array, by turning off the appropriate bits in the layer * mask. Understand that the image for a request will be created based on the * layer array contents and the request layer mask at the time the request * processing is started, not when it is submitted. * <P> * Right now, the ImageServer is single threaded - processing requests one after * another. The request setup was written to support multi-threaded processing, * though, where each image could be generated in it's own thread. That code is * not written - maybe someday. * <P> * <pre> * * # If the ImageServer is created and given a prefix (in this example, * # 'imageServer') the properties file should contain the properties: * imageServer.layers=<layer1 layer2 ...> * layer1.className=<classname> * layer1.prettyName=<pretty name of layer> * # Add other attributes as required by layer1... * layer2.className=<classname> * layer2.prettyName=<pretty name of layer> * # Add other attributes as required by layer2... * # First formatter listed is default. * imageServer.formatters=<formatter1 formatter2 ...> * formatter1.class=<classname of formatter 1> * # Add other formatter1 properties * formatter2.class=<classname of formatter 2> * * </pre> * <P> * NOTE: If you simply hand the ImageServer a standard openmap.properties file, * it works with the addition of the first two attributes except WITHOUT the * 'imageServer.' prefix. * * New for 4.5: If the layers property is not defined, then the openmap.layers * property is used to define which layers are available for the ImageServer. */ public class ImageServer implements /* ImageReadyListener, ImageReceiver, */PropertyConsumer { public static Logger logger = Logger.getLogger("com.bbn.openmap.image.ImageServer"); /** The Image formatter for the output image. */ protected ImageFormatter formatter; /** * Hashtable of ImageFormatters available to be used. */ protected Map<String, ImageFormatter> imageFormatters; /** The array of layers on the map. First is on top. */ protected Layer[] layers; /** Property for space separated layers. */ public static final String ImageServerLayersProperty = "layers"; /** OpenMap prefix */ public static final String OpenMapPrefix = "openmap."; /** * Property for the image formatter list of available formats. This property * should contain a space separated list of marker names. */ public static final String ImageFormattersProperty = "formatters"; /** Property to turn on anti-aliasing. */ public static final String AntiAliasingProperty = "antialiasing"; /** * Property to set the background color. */ public static final String BackgroundProperty = "background"; /** Flag to do graphics and text anti-aliasing on the map image. */ protected boolean doAntiAliasing = false; /** * A place to hold on to a property prefix in case one is used. Useful for * ImageServer properties files where more than one image server is defined. */ protected String propertiesPrefix = null; /** * The ProjectionFactory to be used for image projections. If null, the * default projection set will be used. */ protected ProjectionFactory projectionFactory; private boolean transparent = true; /** * Empty constructor that expects to be configured later. */ protected ImageServer() { } /** * To create the image server, you hand it a set of properties that let it * create an array of layers, and also to set the properties for those * layers. The properties file for the ImageServer looks strikingly similar * to the openmap.properties file. So, all the layers get set up here... */ public ImageServer(Properties props) { setProperties(props); } /** * Same as the other constructor, except that the properties can have a * prefix in front of them. The format of the prefix has to match how the * property is specified the the properties file, which may include the * period - i.e server1.imageServer.layers, the server1. is the prefix that * should get passed in. The ImageMaster does this. */ public ImageServer(String prefix, Properties props) { this(prefix, props, null); } /** * Create an ImageServer that should be configured with a Properties file. * The prefix given is to scope the ImageServer properties to this instance. * The Hashtable is for reusing any layers that may already be instantiated. */ public ImageServer(String prefix, Properties props, Map<String, Layer> instantiatedLayers) { setProperties(prefix, props, instantiatedLayers); } /** * Create an ImageServer from an array of Layers and an ImageFormatter. It's * assumed that the layers are already configured. * * @param layers the array of layers. * @param formatter the ImageFormatter to use for the output image format. */ public ImageServer(Layer[] layers, ImageFormatter formatter) { this.layers = layers; this.formatter = formatter; } /** * Set whether anti-aliasing is used when creating the image. */ public void setDoAntiAliasing(boolean set) { doAntiAliasing = set; } /** * Find out whether anti-aliasing is used when creating the image. */ public boolean getDoAntiAliasing() { return doAntiAliasing; } /** * Set the layers used on the NEXT request that is processed. Will not * affect any image currently being created. * * @param newLayers an array of com.bbn.openmap.Layer objects, already * configured and ready to respond to a projectionChanged method * call. */ public synchronized void setLayers(Layer[] newLayers) { if (newLayers == null) { layers = new Layer[0]; } else { layers = newLayers; } } /** * Retrieve the current set of layers used for requests. * * @return Layer[] */ public synchronized Layer[] getLayers() { return layers; } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. * * @param proj projection of map. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj) { return createImage(proj, -1, -1, 0xFFFFFFFF); } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj, int scaledWidth, int scaledHeight) { return createImage(proj, scaledWidth, scaledHeight, 0xFFFFFFFF); } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. The background used for the * image is the one set in this ImageServer object. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param showLayers Layer marker names reflecting the layers that should be * part of this image. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj, int scaledWidth, int scaledHeight, List<String> showLayers) { return createImage(proj, scaledWidth, scaledHeight, showLayers, getBackground()); } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. The background used for the * image is the one set in this ImageServer object. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param showLayers Layersthat should be part of this image. * @return a byte[] representing the formatted image. */ public byte[] createImageFromLayers(Projection proj, int scaledWidth, int scaledHeight, List<Layer> showLayers) { return createImageFromLayers(proj, scaledWidth, scaledHeight, showLayers, getBackground()); } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. The background used for the * image is the one set in this ImageServer object. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param showLayers Layer marker names reflecting the layers that should be * part of this image. * @param background the Paint to be used for the background of this image. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj, int scaledWidth, int scaledHeight, List<String> showLayers, Paint background) { logger.fine("using the new ProjectionPainter interface! createImage with layer string array."); if (formatter == null) { logger.warning("no formatter set! Can't create image."); return new byte[0]; } ImageFormatter imageFormatter = formatter.makeClone(); java.awt.Graphics graphics = createGraphics(imageFormatter, proj.getWidth(), proj.getHeight()); if (graphics == null) { return new byte[0]; } ((Proj) proj).drawBackground((Graphics2D) graphics, background); if (showLayers != null) { int size = showLayers.size(); for (int j = size - 1; j >= 0; j--) { for (int i = layers.length - 1; i >= 0; i--) { String layerName = (String) showLayers.get(j); Layer layer = layers[i]; String prefix = layer.getPropertyPrefix(); if (prefix == null) { // Just in case the PlugInLayer prefix didn't get set to // the // same as the plugins' if (layer instanceof PlugInLayer) { prefix = ((PlugInLayer) layer).getPlugIn().getPropertyPrefix(); } } if (layerName.equals(prefix)) { layer.renderDataForProjection(proj, graphics); if (logger.isLoggable(Level.FINE)) { logger.fine("image request adding layer graphics from : " + layer.getName()); } } } } } else if (logger.isLoggable(Level.FINE)) { logger.fine("no layers available for image"); } byte[] formattedImage = getFormattedImage(imageFormatter, scaledWidth, scaledHeight); graphics.dispose(); return formattedImage; } /** * Create an image from a set of layers. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param layers A set of layers to paint into the image. * @param background the Paint to be used for the background of this image. * @return a byte[] representing the formatted image. */ public byte[] createImageFromLayers(Projection proj, int scaledWidth, int scaledHeight, List<Layer> layers, Paint background) { logger.fine("using the new ProjectionPainter interface! createImage with layer list."); if (formatter == null) { logger.warning("no formatter set! Can't create image."); return new byte[0]; } ImageFormatter imageFormatter = formatter.makeClone(); java.awt.Graphics graphics = createGraphics(imageFormatter, proj.getWidth(), proj.getHeight()); if (graphics == null) { return new byte[0]; } ((Proj) proj).drawBackground((Graphics2D) graphics, background); if (layers != null && !layers.isEmpty()) { for (int i = layers.size() - 1; i >= 0; i--) { Layer layer = layers.get(i); if (layer != null) { layer.renderDataForProjection(proj, graphics); if (logger.isLoggable(Level.FINE)) { logger.fine("image request adding layer graphics from : " + layer.getName()); } } } } else if (logger.isLoggable(Level.FINE)) { logger.fine("no layers available for image"); } byte[] formattedImage = getFormattedImage(imageFormatter, scaledWidth, scaledHeight); graphics.dispose(); return formattedImage; } /** * This method returns a integer representing a mask created from the * visibility settings of the layers. */ public int calculateVisibleLayerMask() { int ret = 0; // Initialize all the layer bits to zero. for (int i = layers.length - 1; i >= 0; i--) { if (layers[i].isVisible()) { ret |= (0x00000001 << i); } } return ret; } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. Uses the default background * set in the ImageServer. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param includedLayerMask a mask signifying which of the ImageServer * layers to use in the image. It's assumed that the called knows * which layers are desired. Bit 1 of the mask refers to layer[0], * etc. A bit turned on means the layer will be included. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj, int scaledWidth, int scaledHeight, int includedLayerMask) { return createImage(proj, scaledWidth, scaledHeight, includedLayerMask, getBackground()); } /** * Use the ProjectionPainter interface of the layers to create an image. * This approach avoids some of the timing issues that the thread model of * the MapBean and Layers that seem to pop up from time to time. They are * Swing components, you know. They were designed to be part of a GUI. So, * this is a serialized, safe way to do things. * * @param proj projection of map. * @param scaledWidth scaled pixel width of final image. If you don't want * it scaled, use -1. * @param scaledHeight scaled pixel height of final image. If you don't want * it scaled, use -1. * @param includedLayerMask a mask signifying which of the ImageServer * layers to use in the image. It's assumed that the called knows * which layers are desired. Bit 1 of the mask refers to layer[0], * etc. A bit turned on means the layer will be included. * @param background the background Paint to use for the image, behind the * layers. * @return a byte[] representing the formatted image. */ public byte[] createImage(Projection proj, int scaledWidth, int scaledHeight, int includedLayerMask, Paint background) { logger.fine("using the new ProjectionPainter interface! createImage with layer mask."); if (formatter == null) { logger.warning("no formatter set! Can't create image."); return new byte[0]; } ImageFormatter imageFormatter = formatter.makeClone(); Graphics graphics = createGraphics(imageFormatter, proj.getWidth(), proj.getHeight()); if (graphics == null) { return new byte[0]; } ((Proj) proj).drawBackground((Graphics2D) graphics, background); if (logger.isLoggable(Level.FINE)) { logger.fine("considering " + layers.length + " for image..."); } if (layers != null) { for (int i = layers.length - 1; i >= 0; i--) { if ((includedLayerMask & (0x00000001 << i)) != 0) { if (logger.isLoggable(Level.FINE)) { logger.fine("image request adding layer graphics from : " + layers[i].getName()); } layers[i].renderDataForProjection(proj, graphics); } else { if (logger.isLoggable(Level.FINE)) { logger.fine("skipping layer graphics from : " + layers[i].getName()); } } } } else { if (logger.isLoggable(Level.FINE)) { logger.fine("no layers available"); } } byte[] formattedImage = getFormattedImage(imageFormatter, scaledWidth, scaledHeight); graphics.dispose(); return formattedImage; } /** * Create a java.awt.Graphics to use for an image. The Graphics will affect * the image contained within the ImageFormatter. * * @param formatter the ImageFormatter containing the image. * @param width the pixel width of the image. * @param height the pixel height of the image. */ protected Graphics createGraphics(ImageFormatter formatter, int width, int height) { java.awt.Graphics graphics = null; if (formatter == null) { logger.warning("ImageServer.createGraphics: Formatter is null, returning null graphics."); return null; } graphics = formatter.getGraphics(width, height, getTransparent()); if (graphics == null) { logger.warning("ImageServer.createGraphics: NOT able to create Graphics!"); return null; } if (logger.isLoggable(Level.FINE)) { logger.fine("graphics is cool"); } if (doAntiAliasing && graphics instanceof java.awt.Graphics2D) { java.awt.Graphics2D g2d = (java.awt.Graphics2D) graphics; g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } return graphics; } /** * Format the image that is contained in the ImageFormatter, scaling to a * particular size if the scaledWidth and scaledHeight are greater than 0. */ protected byte[] getFormattedImage(ImageFormatter formatter, int scaledWidth, int scaledHeight) { if (logger.isLoggable(Level.FINE)) { logger.fine("ready to create formatted image."); } byte[] formattedImage = null; // Now, scale the image, if needed... if (scaledWidth > 0 && scaledHeight > 0) { formattedImage = formatter.getScaledImageBytes(scaledWidth, scaledHeight); } else { logger.fine("ImageServer: using full scale image (unscaled)."); formattedImage = formatter.getImageBytes(); } return formattedImage; } /** * Set the layers and image type in the properties. */ public void setProperties(Properties props) { setProperties((String) null, props); } /** * Set the layers and image type in the properties. The properties might * have a prefix in the file. */ public void setProperties(String prefix, Properties props) { setProperties(prefix, props, (Map<String, Layer>) null); } /** * Set the layers and image type in the properties. The properties might * have a prefix in the file. */ public void setProperties(String prefix, Properties props, Map<String, Layer> instantiatedLayers) { setPropertyPrefix(prefix); prefix = PropUtils.getScopedPropertyPrefix(prefix); layers = getLayers(props, instantiatedLayers); formatter = getFormatters(props); doAntiAliasing = PropUtils.booleanFromProperties(props, prefix + AntiAliasingProperty, false); background = getBackground(props, prefix + BackgroundProperty); } /** * Determine the background color based on property settings. If the * property key isn't found, the openmap.BackgroundColor property will be * used. If that isn't found, then Color.white will be returned as default. * * @param props properties to check * @param propertyKey first key to check for. * @return Paint used for background, as stated in properties */ public Paint getBackground(Properties props, String propertyKey) { String paintString = props.getProperty(propertyKey); if (paintString == null) { paintString = props.getProperty(Environment.BackgroundColor); } Paint ret = null; if (paintString != null) { try { ret = PropUtils.parseColor(paintString, transparent); } catch (NumberFormatException nfe) { // Color set to white below... } } if (ret == null) { ret = Color.white; } return ret; } /** * Part of the PropertyConsumer interface. */ public Properties getProperties(Properties props) { if (props == null) { props = new Properties(); } String prefix = PropUtils.getScopedPropertyPrefix(this); StringBuffer buf = new StringBuffer(); for (Layer layer : getLayers()) { buf.append(layer.getPropertyPrefix()).append(" "); layer.getProperties(props); } props.put(prefix + ImageServerLayersProperty, buf.toString().trim()); buf = new StringBuffer(); if (imageFormatters != null) { int index = 1; for (ImageFormatter formatter : imageFormatters.values()) { String className = formatter.getClass().getName(); String prfx = className.substring(className.lastIndexOf('.') + 1) + index; if (formatter instanceof PropertyConsumer) { PropertyConsumer pc = (PropertyConsumer) formatter; String opp = pc.getPropertyPrefix(); if (opp != null) { prfx = opp; pc.getProperties(props); } else { pc.setPropertyPrefix(prfx); pc.getProperties(props); // Reset it to whatever it was before we grabbed // properties. pc.setPropertyPrefix(null); } } buf.append(prfx).append(" "); props.put(prfx + ComponentFactory.DotClassNameProperty, formatter.getClass().getName()); index++; } } props.put(prefix + ImageFormattersProperty, buf.toString().trim()); props.put(prefix + AntiAliasingProperty, Boolean.toString(doAntiAliasing)); if (background instanceof Color) { String colorString = Integer.toHexString(((Color) background).getRGB()); props.put(Environment.BackgroundColor, colorString); } return props; } /** * Part of the PropertyConsumer interface. */ public Properties getPropertyInfo(Properties list) { if (list == null) { list = new Properties(); } list.put(ImageServerLayersProperty, "A list of marker names (space-separated) for layer definitions"); list.put(ImageFormattersProperty, "A list of marker names (space-separated) for ImageFormatter definitions"); list.put(AntiAliasingProperty, "Whether to use anti-aliasing for the image"); return list; } /** * Part of the PropertyConsumer interface. Set the Properties prefix to use * to scope the relevant properties passed into the setProperties method. */ public void setPropertyPrefix(String prefix) { propertiesPrefix = prefix; } /** * Part of the PropertyConsumer interface. Get the Properties prefix used to * scope the relevant properties passed into the setProperties method. */ public String getPropertyPrefix() { return propertiesPrefix; } /** * Given a integer that represents, bitwise, the layers that you want out of * the current list held by the ImageServer layer array, return an array of * those layers. * * @param layerMask bit mask for desired layers, bit 0 is layer 0. * @return layer[] */ protected synchronized Layer[] getMaskedLayers(int layerMask) { if (layerMask == 0xFFFFFFFF || layers == null) { // They all want to be there if (logger.isLoggable(Level.FINE)) { logger.fine((layers != null ? "ImageServer: image request adding all layers." : "ImageServer.getMaskedLayers() null layers")); } return layers; } else { // Use the vector as a growable array, and add the layers // to it that the mask says should be there. Vector<Layer> layerVector = new Vector<Layer>(layers.length); for (int i = 0; i < layers.length; i++) { if ((layerMask & (0x00000001 << i)) != 0) { layerVector.add(layers[i]); if (logger.isLoggable(Level.FINE)) { logger.fine("image request adding layer: " + layers[i].getName()); } } } Layer[] imageLayers = new Layer[layerVector.size()]; return (Layer[]) layerVector.toArray(imageLayers); } } /** * Get the ImageFormatter currently used for the image creation. * * @return ImageFormatter. */ public synchronized ImageFormatter getFormatter() { return formatter; } /** * Set the ImageFormatter to be used for ImageCreation. */ public synchronized void setFormatter(ImageFormatter f) { formatter = f; if (imageFormatters == null) { imageFormatters = new Hashtable<String, ImageFormatter>(); } if (!imageFormatters.containsKey(formatter.getFormatLabel())) { imageFormatters.put(formatter.getFormatLabel(), formatter); } } /** * Set the default formatter to the one with the given label. The label can * be retrieved from the ImageFormatter. * * @param formatterLabel String for a particular formatter. * @return true if label matches up with a known formatter, false if no * formatter found. */ public synchronized boolean setFormatter(String formatterLabel) { ImageFormatter tmpFormatter = (ImageFormatter) imageFormatters.get(formatterLabel.intern()); if (tmpFormatter != null) { setFormatter(tmpFormatter); return true; } else { return false; } } /** * Get the Hashtable used to hold the ImageFormatters. The label for each * one is the lookup for it in the Hashtable. * * @return Hashtable of ImageFormatters. */ public synchronized Map<String, ImageFormatter> getFormatters() { return imageFormatters; } /** * Set the ImageFormatter Hashtable to set up the possible choices for image * formats. * * @param iFormatters Hashtable of ImageFormatters * @param defaultFormatterKey the key label of the formatter to use for a * default. */ public synchronized void setFormatters(Map<String, ImageFormatter> iFormatters, String defaultFormatterKey) { imageFormatters = iFormatters; formatter = (ImageFormatter) imageFormatters.get(defaultFormatterKey.intern()); } /** * Create an ImageFormatter from the contents of a properties object. * * @param p Properties used to initialize the Properties. * @return default formatter. */ protected synchronized ImageFormatter getFormatters(Properties p) { String formattersString; ImageFormatter iFormatter = null; String prefix = PropUtils.getScopedPropertyPrefix(this); formattersString = p.getProperty(prefix + ImageFormattersProperty); // First, look at the formatters string to get a marker list // of available formatters. if (formattersString != null) { Vector<String> markerNames = PropUtils.parseSpacedMarkers(formattersString); Vector<?> formatters = ComponentFactory.create(markerNames, p); int size = formatters.size(); if (imageFormatters == null) { imageFormatters = new Hashtable<String, ImageFormatter>(size); } for (int i = 0; i < size; i++) { ImageFormatter formatter = (ImageFormatter) formatters.get(i); imageFormatters.put(formatter.getFormatLabel(), formatter); if (i == 0) { iFormatter = formatter; } } } else { logger.fine("no formatters specified"); } return iFormatter; } /** * Create an array of Layers from a properties object. */ protected Layer[] getLayers(Properties p) { return getLayers(p, (Map<String, Layer>) null); } /** * Create an array of Layers from a properties object. Reuse the layer from * the hashtable if it's there under the same property name. The Hashtable * is kept for an ImageServer that is used buy an ImageMaster or another * object that is using different layers for it's image. It will reuse the * layers it's already created if the marker names are the same. * * @param p properties * @param instantiatedLayers a hashtable containing layers, with the prefix * layer name used as the key. */ protected Layer[] getLayers(Properties p, Map<String, Layer> instantiatedLayers) { String layersValue; String prefix = PropUtils.getScopedPropertyPrefix(this); layersValue = p.getProperty(prefix + ImageServerLayersProperty); if (layersValue == null) { // get openmap.layers value layersValue = p.getProperty(OpenMapPrefix + ImageServerLayersProperty); if (layersValue == null) { logger.warning("No property \"" + ImageServerLayersProperty + "\" found in ImageServer properties."); return new Layer[0]; } } Vector<String> layerNames = PropUtils.parseSpacedMarkers(layersValue); if (logger.isLoggable(Level.FINE)) { logger.fine("OpenMap.getLayers(): " + layerNames); } int nLayerNames = layerNames.size(); List<Layer> layers = new ArrayList<Layer>(nLayerNames); for (String layerName : layerNames) { // Check to see if some other ImageServer has used this // layer, and reuse it. if (instantiatedLayers != null) { Layer iLayer = instantiatedLayers.get(layerName); if (iLayer != null) { // We might want to consider adding this: // iLayer.setProperties(layerName, p); layers.add(iLayer); if (logger.isLoggable(Level.FINE)) { logger.fine("adding instantiated layer /" + layerName + "/"); } continue; } } // Brand new layer, so instantiate it. String classProperty = layerName + ".class"; String className = p.getProperty(classProperty); if (className == null) { logger.warning("Failed to locate property \"" + classProperty + "\""); logger.warning("Skipping layer \"" + layerName + "\""); continue; } Object obj = ComponentFactory.create(className, layerName, p); if (obj instanceof Layer || obj instanceof PlugIn) { Layer l = null; if (obj instanceof PlugIn) { PlugIn pi = (PlugIn) obj; PlugInLayer pil = new PlugInLayer(); pil.setPlugIn(pi); pil.setName(p.getProperty(PropUtils.getScopedPropertyPrefix(pi) + Layer.PrettyNameProperty)); l = pil; } else { l = (Layer) obj; } layers.add(l); if (instantiatedLayers != null) { instantiatedLayers.put(layerName, l); if (logger.isLoggable(Level.FINE)) { logger.fine("Saving /" + layerName + "/ to instantiated layers hashtable."); } } } } int nLayers = layers.size(); if (nLayers == 0) { return new Layer[0]; } else { Layer[] layerArray = new Layer[nLayers]; layers.toArray(layerArray); return layerArray; } } public ProjectionFactory getProjectionFactory() { if (projectionFactory == null) { projectionFactory = ProjectionFactory.loadDefaultProjections(); } return projectionFactory; } public void setProjectionFactory(ProjectionFactory projFactory) { projectionFactory = projFactory; } /** * Takes a byte array and writes it out to a file path. * * @param imageBytes the formatted bytes of the image. * @param outputPath the path of the image file. * @param checkFormatterForExtension if true, will check the current active * formatter for extension that will be added to the path if it * doesn't end with the image type. * @return the final file path used, with any extensions added. * @throws IOException */ public String writeImageFile(byte[] imageBytes, String outputPath, boolean checkFormatterForExtension) throws IOException { String appendix = ""; if (checkFormatterForExtension) { ImageFormatter formatter = getFormatter(); if (formatter == null) { appendix = ".jpg"; } else { String fileType = formatter.getFormatLabel(); if (fileType.equals(WMTConstants.IMAGEFORMAT_JPEG)) { appendix = ".jpg"; } else { appendix = "." + fileType.toLowerCase(); } } // If the file output path already ends properly, don't bother // changing // it. if (outputPath.endsWith(appendix)) { appendix = ""; } } String finalOutputPath = outputPath + appendix; FileOutputStream fos = new FileOutputStream(finalOutputPath); fos.write(imageBytes); fos.flush(); fos.close(); return finalOutputPath; } /** * For convenience, to create an image file based on the contents of a * properties file (like an openmap.properties file). * * @param prefix The prefix for the ImageServer properties (layers and * formatters) to use in the properties file. If defined, then this * method will look for 'prefix.layers' and prefix.formatters' * properties. If null, then this method will look 'layers' and * 'formatters' properties. * * @param props The properties to use for defining the layers and plugins to * use on the map image. Standard openmap.properties formats for * layer definitions. See the standard openmap.properties file for * more details on how to define layers and plugins. * * @param proj The projection to use for the map. If null, then the * Environment projection properties will be looked for in the * Properties. * * @param outputPath The output path for the image file. The image file * should not have an appendix defined. This method will check which * formatter is being used, and will assign one based on the image * format (leave off the ., too). * * @return the final path of the written image file, with the chosen * appendix attached. */ public static String createImageFile(String prefix, Properties props, Projection proj, String outputPath) throws MalformedURLException, IOException { String appendix = ""; ImageServer is = new ImageServer(props); ImageFormatter formatter = is.getFormatter(); if (formatter == null) { is.setFormatter(new SunJPEGFormatter()); appendix = ".jpg"; } else { String fileType = formatter.getFormatLabel(); if (fileType.equals(WMTConstants.IMAGEFORMAT_JPEG)) { appendix = ".jpg"; } else { appendix = "." + fileType.toLowerCase(); } } Color background = MapBean.DEFAULT_BACKGROUND_COLOR; background = (Color) PropUtils.parseColorFromProperties(props, Environment.BackgroundColor, background); is.setBackground(background); // Initialize the map projection, scale, center with // user prefs or defaults if (proj == null) { ProjectionFactory projFactory = is.getProjectionFactory(); String projName = props.getProperty(Environment.Projection); Class<? extends Projection> projClass = projFactory.getProjClassForName(projName); if (projClass == null) { projClass = Mercator.class; } Point2D center = null; if (GeoProj.class.isAssignableFrom(projClass)) { center = new LatLonPoint.Float(PropUtils.floatFromProperties(props, Environment.Latitude, 0f), PropUtils.floatFromProperties(props, Environment.Longitude, 0f)); } else { center = new Point2D.Float(PropUtils.floatFromProperties(props, Environment.Latitude, 0f), PropUtils.floatFromProperties(props, Environment.Longitude, 0f)); } proj = projFactory.makeProjection(projClass, center, PropUtils.floatFromProperties(props, Environment.Scale, MapBean.DEFAULT_SCALE), PropUtils.intFromProperties(props, Environment.Width, MapBean.DEFAULT_WIDTH), PropUtils.intFromProperties(props, Environment.Height, MapBean.DEFAULT_HEIGHT)); } if (logger.isLoggable(Level.FINE)) { logger.fine("creating image with projection " + proj); } byte[] imageBytes = is.createImage(proj); String finalOutputPath = outputPath + appendix; FileOutputStream fos = new FileOutputStream(finalOutputPath); fos.write(imageBytes); fos.flush(); fos.close(); return finalOutputPath; } /** * Paint object used for map backgrounds. */ protected Paint background; /** * Set the Paint to use for image backgrounds. */ public void setBackground(Paint bg) { background = bg; } /** * Get the Paint to use for image backgrounds. */ public Paint getBackground() { return background; } /** * Set the transparent flag. Even if this flag is true, the image still may * not end up transparent if the {@link ImageFormatter} does not support * transparency or the image is completely filled. * * @param transparent */ public void setTransparent(boolean transparent) { this.transparent = transparent; } /** * Get the transparent flag. Even if this flag is true, the image still may * not end up transparent if the {@link ImageFormatter} does not support * transparency or the image is completely filled. * * @return true if set for transparency */ public boolean getTransparent() { return transparent; } /** * The ImageServer class main function will create a map image from a * modified openmap.properties file. * * <pre> * java com.bbn.openmap.image.ImageServer -properties (path to properties file) -file (path to output image) * </pre> * * <P> * The path to the output image should not have an appendix on it, that will * get assigned depending on what image format is used. */ public static void main(String[] argv) { com.bbn.openmap.util.ArgParser ap = new com.bbn.openmap.util.ArgParser("ImageServer"); ap.add("properties", "The properties file to use for the image.", 1); ap.add("file", "The output image file, without appendix (default is 'image').", 1); if (!ap.parse(argv)) { ap.printUsage(); System.exit(0); } String imagefile = "image"; String arg[]; arg = ap.getArgValues("file"); if (arg != null) { imagefile = arg[0]; } Properties props = null; arg = ap.getArgValues("properties"); if (arg != null) { String ps = arg[0]; try { URL url = PropUtils.getResourceOrFileOrURL(null, ps); InputStream inputStream = url.openStream(); props = new Properties(); props.load(inputStream); Projection proj = null; String finalOutputPath = ImageServer.createImageFile(null, props, proj, imagefile); if (logger.isLoggable(Level.FINE)) { logger.fine("Writing image file to: " + finalOutputPath); } } catch (MalformedURLException murle) { logger.warning("ImageServer can't find properties file: " + arg[0]); } catch (IOException ioe) { logger.warning("ImageServer can't write output image: IOException"); } } System.exit(0); } }