// **********************************************************************
//
// <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);
}
}