// **********************************************************************
//
// <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/proj/ProjectionFactory.java,v $
// $RCSfile: ProjectionFactory.java,v $
// $Revision: 1.15 $
// $Date: 2007/04/17 20:25:08 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.proj;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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.MapBean;
import com.bbn.openmap.MapHandler;
import com.bbn.openmap.OMComponent;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.SoloMapComponent;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.PropUtils;
/**
* The ProjectionFactory creates Projections. It used to have Projection classes
* hard-coded into it which were accessible through static methods, but this
* paradigm has been changed slightly so the ProjectionFactory is a
* SoloMapComponent added to the MapHandler. It will attach itself to a MapBean
* if it finds one in the MapHandler.
* <P>
*
* The ProjectionFactory can look for ProjectionLoaders in the MapHandler to
* dynamically add projections to the factory. The ProjectionHandler can also
* create ProjectionLoaders from property settings. Changes to the available
* projections can be discovered through property changes.
* <P>
*/
public class ProjectionFactory extends OMComponent implements SoloMapComponent {
/**
* Center lat/lon property parameter for new projections passed to
* ProjectionLoader.
*/
public final static String CENTER = "CENTER";
/**
* Scale property parameter for new projections passed to ProjectionLoader.
*/
public final static String SCALE = "SCALE";
/**
* Projection height (pixels) property parameter for new projections passed
* to ProjectionLoader.
*/
public final static String HEIGHT = "HEIGHT";
/**
* Projection width (pixels) property parameter for new projections passed
* to ProjectionLoader.
*/
public final static String WIDTH = "WIDTH";
/**
* Datum property parameter for new projections passed to ProjectionLoader.
*/
public final static String DATUM = "DATUM";
/**
* The property name that is fired when the list of available projections
* has changed.
*/
public final static String AvailableProjectionProperty = "AvailableProjections";
/**
* ProjectionFactory property used to designate new projection loaders that
* should be created from properties.
*/
public final static String ProjectionLoadersProperty = "projectionLoaders";
/**
* PropertyChangeSupport for letting listeners know about new projections
* that are available from the factory.
*/
protected PropertyChangeSupport pcs;
protected Vector<ProjectionLoader> projLoaders = new Vector<ProjectionLoader>();
/**
*
*/
public ProjectionFactory() {
pcs = new PropertyChangeSupport(this);
}
/**
* Check the properties for those to create ProjectionLoaders.
*/
public void setProperties(String prefix, Properties props) {
super.setProperties(prefix, props);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
String loaderPrefixesString = props.getProperty(prefix + ProjectionLoadersProperty);
if (loaderPrefixesString != null) {
Vector<String> loaderPrefixes = PropUtils.parseSpacedMarkers(loaderPrefixesString);
Vector<?> loaders = ComponentFactory.create(loaderPrefixes, prefix, props);
for (Iterator<?> it = loaders.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj instanceof ProjectionLoader) {
projLoaders.add((ProjectionLoader) obj);
}
}
}
}
/**
* Create the properties to create ProjectionLoaders that this loader
* created.
*/
public Properties getProperties(Properties props) {
props = super.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(this);
if (projLoaders != null) {
StringBuffer markerlist = new StringBuffer();
int count = 0;
for (Iterator<ProjectionLoader> it = projLoaders.iterator(); it.hasNext();) {
ProjectionLoader pl = it.next();
String markerName;
if (pl instanceof PropertyConsumer) {
PropertyConsumer pc = (PropertyConsumer) pl;
markerName = PropUtils.getScopedPropertyPrefix(pc.getPropertyPrefix());
// Need to do this here before the marker name
// potentially changes
props.put(markerName + "class", pl.getClass().getName());
if (markerName.startsWith(prefix)) {
markerName = markerName.substring(prefix.length());
}
pc.getProperties(props);
} else {
markerName = "projectionLoader" + (count++);
// Need to do this here for any projection loaders
// that aren't property consumers.
props.put(markerName + ".class", pl.getClass().getName());
}
markerlist.append(" ").append(markerName);
}
props.put(prefix + ProjectionLoadersProperty, markerlist.toString());
}
return props;
}
/**
* Returns an array of Projection names available from this factory.
*/
public String[] getAvailableProjections() {
// duplicate List to handle multiple simultaneous requests
List<ProjectionLoader> projLoaders = new ArrayList<ProjectionLoader>(getProjectionLoaders());
int nProjections = projLoaders.size();
String projNames[] = new String[nProjections];
int i = 0;
for (Iterator<ProjectionLoader> it = projLoaders.iterator(); it.hasNext();) {
projNames[i++] = it.next().getPrettyName();
}
return projNames;
}
/**
* Return the Projection Class with the given pretty name.
*
* @param name the name of the projection, set in the pretty name of it's
* ProjectionLoader.
* @return Class of Projection, or null if not found.
*/
public Class<? extends Projection> getProjClassForName(String name) {
if (name != null) {
for (Iterator<ProjectionLoader> it = getProjectionLoaders().iterator(); it.hasNext();) {
ProjectionLoader loader = it.next();
if (name.equalsIgnoreCase(loader.getPrettyName())) {
return loader.getProjectionClass();
}
}
// If there wasn't a class with the pretty name, check to
// make sure it wasn't a class name itself. If it fails,
// return null. We just want to do this in case people
// start using class names for pretty names.
try {
return (Class<? extends Projection>) Class.forName(name);
} catch (ClassNotFoundException cnfe) {
}
}
return null;
}
/**
* Makes a new projection based on the given projection class name and
* parameters from the given projection.
*/
public Projection makeProjection(String projClassName, Projection p) {
Point2D ctr = p.getCenter();
return makeProjection(projClassName, ctr, p.getScale(), p.getWidth(), p.getHeight());
}
/**
* Create a projection. If the Class for the classname can't be found, a
* Mercator projection will be returned.
*
* @param projClassName the classname of the projection.
* @param center Point2D center of the projection.
* @param scale float scale.
* @param width pixel width of projection.
* @param height pixel height of projection.
* @return Projection
*/
public Projection makeProjection(String projClassName, Point2D center, float scale, int width,
int height) {
if (projClassName == null) {
throw new ProjectionException("No projection class name specified");
}
return makeProjection(getProjClassForName(projClassName), center, scale, width, height);
}
/**
* Create a projection. If the class can't be found, a Mercator projection
* will be returned.
*
* @param projClass the class of the projection.
* @param center Point2D center of the projection.
* @param scale float scale.
* @param width pixel width of projection.
* @param height pixel height of projection.
* @return Projection
*/
public Projection makeProjection(Class<? extends Projection> projClass, Point2D center,
float scale, int width, int height) {
ProjectionLoader loader = MercatorLoader.defaultMercator;
for (Iterator<ProjectionLoader> it = iterator(); it.hasNext();) {
ProjectionLoader pl = (ProjectionLoader) it.next();
if (pl.getProjectionClass() == projClass) {
loader = pl;
}
}
return makeProjection(loader, center, scale, width, height);
}
/**
* Looks at the Environment settings for the default projection and returns
* a Projection suited for those settings. If there is a problem creating
* the projection, the default projection of the MapBean will be returned.
* The ProjectionFactory needs to be loaded with the Projection class
* described in the properties before this will return an expected
* projection.
*
* @return Projection from Environment settings.
*/
public Projection getDefaultProjectionFromEnvironment(Environment e) {
return getDefaultProjectionFromEnvironment(e, 0, 0);
}
/**
* Looks at the Environment settings for the default projection and returns
* a Projection suited for those settings. If there is a problem creating
* the projection, the default projection of the MapBean will be returned.
* The ProjectionFactory needs to be loaded with the Projection class
* described in the properties before this will return an expected
* projection.
*
* @param width pixel height of projection. If 0 or less, the
* Environment.Width value will be used.
* @param height pixel height of projection. If 0 or less, the
* Environment.Height value will be used.
* @return Projection from Environment settings, fit for the pixel height
* and width provided.
*/
public Projection getDefaultProjectionFromEnvironment(Environment environment, int width,
int height) {
// Initialize the map projection, scale, center
// with user prefs or defaults
Projection proj = null;
int w = (width <= 0) ? Environment.getInteger(Environment.Width, MapBean.DEFAULT_WIDTH)
: width;
int h = (height <= 0) ? Environment.getInteger(Environment.Height, MapBean.DEFAULT_HEIGHT)
: height;
try {
proj = makeProjection(Environment.get(Environment.Projection), new LatLonPoint.Float(environment.getFloat(Environment.Latitude, 0f), environment.getFloat(Environment.Longitude, 0f)), environment.getFloat(Environment.Scale, Float.POSITIVE_INFINITY), w, h);
} catch (com.bbn.openmap.proj.ProjectionException pe) {
if (getLogger().isLoggable(Level.FINE)) {
getLogger().fine("Can't use ("
+ Environment.Projection
+ " = "
+ Environment.get(Environment.Projection)
+ ") property as a projection class, need a class name instead. Using default of com.bbn.openmap.proj.Mercator.");
}
proj = makeProjection(Mercator.class, new LatLonPoint.Float(environment.getFloat(Environment.Latitude, 0f), environment.getFloat(Environment.Longitude, 0f)), environment.getFloat(Environment.Scale, Float.POSITIVE_INFINITY), w, h);
}
return proj;
}
/**
* Call the provided ProjectionLoader to create the projection with the
* given parameters. The parameters are converted to Properties before being
* passed to the ProjectionLoader.
*
* @param loader ProjectionLoader for projection type
* @param center center coordinate for projection
* @param scale scale for projection
* @param width pixel width of projection
* @param height pixel height of projection
* @return Projection
*/
public Projection makeProjection(ProjectionLoader loader, Point2D center, float scale,
int width, int height) {
return makeProjection(loader, center, scale, width, height, null);
}
/**
* Call the provided ProjectionLoader to create the projection with the
* given parameters. The parameters are converted to Properties before being
* passed to the ProjectionLoader. The ProjectionLoader should throw a
* ProjectionException from here if it has a problem creating the projection
* with the provided parameters.
*
* @param loader projection loader to use.
* @param center Point2D center of the projection.
* @param scale float scale.
* @param width pixel width of projection.
* @param height pixel height of projection.
* @param projProps a Properties object to add the parameters to, which can
* include extra parameters that are needed by this particular
* projection loader. If null, a Properties object will be created.
* @return projection, or null if the projection can't be created.
*/
public Projection makeProjection(ProjectionLoader loader, Point2D center, float scale,
int width, int height, Properties projProps) {
Projection proj = null;
if (loader == null) {
getLogger().warning("not given a ProjectionLoader to use to create a Projection");
return proj;
}
if (projProps == null) {
projProps = new Properties();
}
projProps.put(CENTER, center);
projProps.put(SCALE, Float.toString(scale));
projProps.put(WIDTH, Integer.toString(width));
projProps.put(HEIGHT, Integer.toString(height));
proj = loader.create(projProps);
if (proj == null) {
getLogger().warning("tried to create a Projection from a " + loader.getPrettyName()
+ ", " + loader.getProjectionClass().getName() + ", failed.");
}
return proj;
}
public void addProjectionLoader(ProjectionLoader loader) {
projLoaders.add(loader);
fireLoadersChanged();
}
public boolean removeProjectionLoader(ProjectionLoader loader) {
boolean removed = projLoaders.remove(loader);
if (removed) {
fireLoadersChanged();
}
return removed;
}
public void clearProjectionLoaders() {
if (!projLoaders.isEmpty()) {
projLoaders.clear();
fireLoadersChanged();
}
}
public Iterator<ProjectionLoader> iterator() {
return projLoaders.iterator();
}
public int numProjections() {
return projLoaders.size();
}
public Collection<ProjectionLoader> getProjectionLoaders() {
return Collections.unmodifiableCollection(projLoaders);
}
protected void fireLoadersChanged() {
pcs.firePropertyChange(AvailableProjectionProperty, null, projLoaders);
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
if (pcl != null) {
pcs.addPropertyChangeListener(pcl);
pcl.propertyChange(new PropertyChangeEvent(this, AvailableProjectionProperty, null, projLoaders));
}
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener pcl) {
if (pcl != null) {
pcs.addPropertyChangeListener(propertyName, pcl);
pcl.propertyChange(new PropertyChangeEvent(this, AvailableProjectionProperty, null, projLoaders));
}
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(propertyName, pcl);
}
/**
* Using the MapHandler to find ProjectionLoaders being added from the
* application.
*/
public void findAndInit(Object obj) {
if (obj instanceof ProjectionLoader) {
addProjectionLoader((ProjectionLoader) obj);
}
if (obj instanceof MapBean) {
((MapBean) obj).setProjectionFactory(this);
}
}
/**
* Using the MapHandler to find ProjectionLoaders being removed from the
* application.
*/
public void findAndUndo(Object obj) {
if (obj instanceof ProjectionLoader) {
removeProjectionLoader((ProjectionLoader) obj);
}
}
/**
* Convenience method to load a ProjectionFactory and default projections
* into the provided MapHandler.
*
* @param mapHandler the MapHandler to receive the default ProjectionFactory
*/
public static void loadDefaultProjections(MapHandler mapHandler) {
mapHandler.add(loadDefaultProjections(new ProjectionFactory()));
}
/**
* Convenience method to load default projections into a ProjectionFactory
* that will be created.
*
* @return ProjectionFactory
*/
public static ProjectionFactory loadDefaultProjections() {
return loadDefaultProjections(new ProjectionFactory());
}
/**
* Convenience method to load default projections into a ProjectionFactory.
*
* @param pf
* @return ProjectionFactory
*/
public static ProjectionFactory loadDefaultProjections(ProjectionFactory pf) {
if (pf != null && pf.numProjections() == 0) {
pf.addProjectionLoader(new com.bbn.openmap.proj.MercatorLoader());
pf.addProjectionLoader(new com.bbn.openmap.proj.OrthographicLoader());
pf.addProjectionLoader(new com.bbn.openmap.proj.CADRGLoader());
pf.addProjectionLoader(new com.bbn.openmap.proj.LLXYLoader());
pf.addProjectionLoader(new com.bbn.openmap.proj.GnomonicLoader());
pf.addProjectionLoader(new com.bbn.openmap.proj.CartesianLoader());
}
return pf;
}
// <editor-fold defaultstate="collapsed" desc="Logger Code">
/**
* Holder for this class's Logger. This allows for lazy initialization of
* the logger.
*/
private static final class LoggerHolder {
/**
* The logger for this class
*/
private static final Logger LOGGER = Logger.getLogger(ProjectionFactory.class.getName());
/**
* Prevent instantiation
*/
private LoggerHolder() {
throw new AssertionError("This should never be instantiated");
}
}
/**
* Get the logger for this class.
*
* @return logger for this class
*/
private static Logger getLogger() {
return LoggerHolder.LOGGER;
}
// </editor-fold>
}