// **********************************************************************
//
// <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.5.2.7 $
// $Date: 2007/04/05 21:24:22 $
// $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.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
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.Mercator;
import com.bbn.openmap.proj.Proj;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.ProjectionFactory;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
/**
* The image server is the class you want to deal with when creating images from
* the ImageGenerator. It takes a properties file and sets up the image
* generator 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>
* <code><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></code>
* <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 {
/** The Image formatter for the output image. */
protected ImageFormatter formatter;
/**
* Hashtable of ImageFormatters available to be used.
*/
protected Hashtable 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 propery
* 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;
/**
* 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,
Hashtable 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 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 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 showLayers,
Paint background) {
Debug.message("imageserver",
"ImageServer: using the new ProjectionPainter interface! createImage with layer string array. ");
if (formatter == null) {
Debug.error("ImageServer.createImage: 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);
int size = showLayers.size();
if (showLayers != null) {
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];
if (layerName.equals(layer.getPropertyPrefix())) {
layer.renderDataForProjection(proj, graphics);
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: image request adding layer graphics from : "
+ layer.getName());
}
}
}
}
} else if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: 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 = 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) {
Debug.message("imageserver",
"ImageServer: using the new ProjectionPainter interface! createImage with layer mask.");
if (formatter == null) {
Debug.error("ImageServer.createImage: 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 (Debug.debugging("imageserver")) {
Debug.output("ImageServer: considering " + layers.length
+ " for image...");
}
if (layers != null) {
for (int i = layers.length - 1; i >= 0; i--) {
if ((includedLayerMask & (0x00000001 << i)) != 0) {
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: image request adding layer graphics from : "
+ layers[i].getName());
}
layers[i].renderDataForProjection(proj, graphics);
} else {
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: skipping layer graphics from : "
+ layers[i].getName());
}
}
}
} else {
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: 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) {
graphics = formatter.getGraphics(width, height);
} else {
Debug.error("ImageServer.createGraphics: Formatter is null, returning null graphics.");
return null;
}
if (graphics == null) {
Debug.error("ImageServer.createGraphics: NOT able to create Graphics!");
return null;
}
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer.createGraphics: 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 (Debug.debugging("imageserver")) {
Debug.output("ImageServer: 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 {
Debug.message("imageserver",
"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, (Hashtable) 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,
Hashtable 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
*/
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);
} catch (NumberFormatException nfe) {
// Color set to white below...
}
}
if (ret == null) {
ret = Color.white;
}
return ret;
}
/**
* Part of the PropertyConsumer interface. Doesn't do anything yet.
*/
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
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
Debug.message("imageserver",
(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 layerVector = new Vector(layers.length);
for (int i = 0; i < layers.length; i++) {
if ((layerMask & (0x00000001 << i)) != 0) {
layerVector.add(layers[i]);
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: 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;
}
/**
* 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 Hashtable 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(Hashtable 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 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 markerNames = PropUtils.parseSpacedMarkers(formattersString);
Vector formatters = ComponentFactory.create(markerNames, p);
int size = formatters.size();
if (imageFormatters == null) {
imageFormatters = new Hashtable(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 {
Debug.message("imageserver",
"ImageServer.getFormatters: no formatters specified");
}
return iFormatter;
}
/**
* Create an array of Layers from a properties object.
*/
protected Layer[] getLayers(Properties p) {
return getLayers(p, (Hashtable) 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, Hashtable 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) {
Debug.error("ImageServer: No property \""
+ ImageServerLayersProperty
+ "\" found in ImageServer properties.");
return new Layer[0];
}
}
Vector layerNames = PropUtils.parseSpacedMarkers(layersValue);
if (Debug.debugging("imageserver")) {
Debug.output("OpenMap.getLayers(): " + layerNames);
}
int nLayerNames = layerNames.size();
Vector layers = new Vector(nLayerNames);
for (int i = 0; i < nLayerNames; i++) {
String layerName = (String) layerNames.elementAt(i);
// Check to see if some other ImageServer has used this
// layer, and reuse it.
if (instantiatedLayers != null) {
Layer iLayer = (Layer) instantiatedLayers.get(layerName);
if (iLayer != null) {
// We might want to consider adding this:
// iLayer.setProperties(layerName, p);
layers.add(iLayer);
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: adding instantiated layer /"
+ layerName + "/");
}
continue;
}
}
// Brand new layer, so instantiate it.
String classProperty = layerName + ".class";
String className = p.getProperty(classProperty);
if (className == null) {
Debug.error("Failed to locate property \"" + classProperty
+ "\"");
Debug.error("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.addElement(l);
if (instantiatedLayers != null) {
instantiatedLayers.put(layerName, l);
if (Debug.debugging("imageserver")) {
Debug.output("ImageServer: Saving /" + layerName
+ "/ to instantiated layers hashtable.");
}
}
}
}
int nLayers = layers.size();
if (nLayers == 0) {
return new Layer[0];
} else {
Layer[] value = new Layer[nLayers];
layers.copyInto(value);
return value;
}
}
// protected void finalize() {
// if (Debug.debugging("gc")) {
// Debug.output("ImageServer: GC'd.");
// }
// }
/**
* 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) {
String projName = props.getProperty(Environment.Projection);
Class projClass = ProjectionFactory.getProjClassForName(projName);
if (projClass == null) {
projClass = Mercator.class;
}
proj = ProjectionFactory.makeProjection(projClass,
PropUtils.floatFromProperties(props,
Environment.Latitude,
0f),
PropUtils.floatFromProperties(props,
Environment.Longitude,
0f),
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 (Debug.debugging("imageserver")) {
Debug.output("ImageServer: 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;
}
/**
* 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) {
Debug.init();
Debug.put("imageserver");
Debug.put("image");
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 {
ProjectionFactory.loadDefaultProjections();
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 (Debug.debugging("imageserver")) {
Debug.output("Writing image file to: " + finalOutputPath);
}
} catch (MalformedURLException murle) {
Debug.error("ImageServer can't find properties file: " + arg[0]);
} catch (IOException ioe) {
Debug.error("ImageServer can't write output image: IOException");
}
}
System.exit(0);
}
}