/* Copyright (c) 2001, 2003 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.wms.responses;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.factory.FactoryFinder;
import org.geotools.filter.Filter;
import org.geotools.map.DefaultMapLayer;
import org.geotools.map.MapLayer;
import org.geotools.referencing.CRS;
import org.geotools.styling.Style;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.vfny.geoserver.Request;
import org.vfny.geoserver.Response;
import org.vfny.geoserver.ServiceException;
import org.vfny.geoserver.config.WMSConfig;
import org.vfny.geoserver.global.FeatureTypeInfo;
import org.vfny.geoserver.global.GeoServer;
import org.vfny.geoserver.global.Service;
import org.vfny.geoserver.wms.GetMapProducer;
import org.vfny.geoserver.wms.GetMapProducerFactorySpi;
import org.vfny.geoserver.wms.WMSMapContext;
import org.vfny.geoserver.wms.WmsException;
import org.vfny.geoserver.wms.requests.GetMapRequest;
import com.vividsolutions.jts.geom.Envelope;
/**
* A GetMapResponse object is responsible of generating a map based on a GetMap
* request. The way the map is generated is independent of this class, wich
* will use a delegate object based on the output format requested
*
* @author Gabriel Roldan, Axios Engineering
* @version $Id: GetMapResponse.java,v 1.11 2004/03/14 23:29:30 groldan Exp $
*/
public class GetMapResponse implements Response {
/** DOCUMENT ME! */
private static final Logger LOGGER = Logger.getLogger(GetMapResponse.class.getPackage()
.getName());
/**
* The map producer that will be used for the production of a map in the
* requested format.
*/
private GetMapProducer delegate;
/**
* The map context
*/
private WMSMapContext map;
/**
* WMS configuration
*/
private WMSConfig config;
/**
* Creates a new GetMapResponse object.
*/
public GetMapResponse(WMSConfig config) {
this.config = config;
}
/**
* DOCUMENT ME!
*
* @param req DOCUMENT ME!
*
* @throws ServiceException DOCUMENT ME!
* @throws WmsException DOCUMENT ME!
*/
public void execute(Request req) throws ServiceException {
GetMapRequest request = (GetMapRequest) req;
final String outputFormat = request.getFormat();
this.delegate = getDelegate(outputFormat, config);
final FeatureTypeInfo[] layers = request.getLayers();
final Style[] styles = (Style[])request.getStyles().toArray(new Style[]{});
//JD:make instance variable in order to release resources later
//final WMSMapContext map = new WMSMapContext();
map = new WMSMapContext();
//DJB: the WMS spec says that the request must not be 0 area
// if it is, throw a service exception!
Envelope env = request.getBbox();
if (env.isNull() || (env.getWidth() <=0)|| (env.getHeight() <=0)){
throw new WmsException("The request bounding box has zero area: " + env);
}
// DJB DONE: replace by setAreaOfInterest(Envelope,
// CoordinateReferenceSystem)
// with the user supplied SRS parameter
//if there's a crs in the request, use that. If not, assume its 4326
CoordinateReferenceSystem mapcrs = request.getCrs();
//DJB: added this to be nicer about the "NONE" srs.
if (mapcrs !=null)
map.setAreaOfInterest(request.getBbox(),mapcrs);
else
map.setAreaOfInterest(request.getBbox());
map.setMapWidth(request.getWidth());
map.setMapHeight(request.getHeight());
map.setBgColor(request.getBgColor());
map.setTransparent(request.isTransparent());
LOGGER.fine("setting up map");
MapLayer layer;
FeatureSource source;
for (int i = 0; i < layers.length; i++) {
Style style = styles[i];
try {
source = layers[i].getFeatureSource();
} catch (IOException exp) {
LOGGER.log(Level.SEVERE,
"Getting feature source: " + exp.getMessage(), exp);
throw new WmsException(null,
"Internal error : " + exp.getMessage());
}
layer = new DefaultMapLayer(source, style);
Filter definitionFilter = layers[i].getDefinitionQuery();
if (definitionFilter != null) {
Query definitionQuery = new DefaultQuery(source.getSchema()
.getTypeName(),
definitionFilter);
layer.setQuery(definitionQuery);
}
map.addLayer(layer);// mapcontext can leak memory -- we make sure we done (see finally block)
}
this.delegate.produceMap(map);
}
/**
* asks the internal GetMapDelegate for the MIME type of the map that it
* will generate or is ready to, and returns it
*
* @param gs DOCUMENT ME!
*
* @return the MIME type of the map generated or ready to generate
*
* @throws IllegalStateException if a GetMapDelegate is not setted yet
*/
public String getContentType(GeoServer gs) throws IllegalStateException {
if (this.delegate == null) {
throw new IllegalStateException("No request has been processed");
}
return this.delegate.getContentType();
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public String getContentEncoding() {
LOGGER.finer("returning content encoding null");
return null;
}
/**
* if a GetMapDelegate is set, calls it's abort method. Elsewere do
* nothing.
*
* @param gs DOCUMENT ME!
*/
public void abort(Service gs) {
if (this.delegate != null) {
LOGGER.fine("asking delegate for aborting the process");
this.delegate.abort();
}
}
/**
* delegates the writing and encoding of the results of the request to the
* <code>GetMapDelegate</code> wich is actually processing it, and has
* been obtained when <code>execute(Request)</code> was called
*
* @param out the output to where the map must be written
*
* @throws ServiceException if the delegate throws a ServiceException
* inside its <code>writeTo(OuptutStream)</code>, mostly due to
* @throws IOException if the delegate throws an IOException inside its
* <code>writeTo(OuptutStream)</code>, mostly due to
* @throws IllegalStateException if this method is called before
* <code>execute(Request)</code> has succeed
*/
public void writeTo(OutputStream out) throws ServiceException, IOException {
try { // mapcontext can leak memory -- we make sure we done (see finally block)
if (this.delegate == null) {
throw new IllegalStateException(
"No GetMapDelegate is setted, make sure you have called execute and it has succeed");
}
LOGGER.finer("asking delegate for write to " + out);
this.delegate.writeTo(out);
}
finally {
try{
map.clearLayerList();
}
catch(Exception e) // we dont want to propogate a new error
{
e.printStackTrace();
}
}
}
/**
* Creates a GetMapDelegate specialized in generating the requested map
* format
*
* @param outputFormat a request parameter object wich holds the processed
* request objects, such as layers, bbox, outpu format, etc.
*
* @return A specialization of <code>GetMapDelegate</code> wich can produce
* the requested output map format
*
* @throws WmsException if no specialization is configured for the output
* format specified in <code>request</code> or if it can't be
* instantiated
*/
static GetMapProducer getDelegate(String outputFormat, WMSConfig config)
throws WmsException {
LOGGER.finer("request format is " + outputFormat);
GetMapProducerFactorySpi mpf = null;
Iterator mpfi = FactoryFinder.factories(GetMapProducerFactorySpi.class);
while (mpfi.hasNext()) {
mpf = (GetMapProducerFactorySpi) mpfi.next();
if (mpf.canProduce(outputFormat)) {
break;
}
mpf = null;
}
if (mpf == null) {
throw new WmsException("There is no support for creating maps in "
+ outputFormat + " format", "InvalidFormat");
}
GetMapProducer producer = mpf.createMapProducer(outputFormat,config);
return producer;
}
/**
* Convenient mehtod to inspect the available
* <code>GetMapProducerFactorySpi</code> and return the set of all the map
* formats' MIME types that the producers can handle
*
* @return a Set<String> with the supported mime types.
*/
public static Set getMapFormats() {
Set mapFormats = new HashSet();
GetMapProducerFactorySpi mpf;
Iterator mpfi = FactoryFinder.factories(GetMapProducerFactorySpi.class);
while (mpfi.hasNext()) {
mpf = (GetMapProducerFactorySpi) mpfi.next();
mapFormats.addAll(mpf.getSupportedFormats());
}
return mapFormats;
}
}