/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.svg;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGGraphics2D;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.DefaultWebMapService;
import org.geoserver.wms.GetMapOutputFormat;
import org.geoserver.wms.MapProducerCapabilities;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.map.MaxErrorEnforcer;
import org.geoserver.wms.map.RenderExceptionStrategy;
import org.geotools.map.MapContent;
import org.geotools.renderer.lite.StreamingRenderer;
import org.w3c.dom.Document;
/**
* Renders svg using the Batik SVG Toolkit. An SVG context is created for a map and then passed of
* to {@link org.geotools.renderer.lite.StreamingRenderer}.
*
* @author Justin Deoliveira, The Open Planning Project
*
*/
public final class SVGBatikMapOutputFormat implements GetMapOutputFormat {
/**
* Default capabilities for SVG format.
*
* <p>
* <ol>
* <li>tiled = unsupported</li>
* <li>multipleValues = unsupported</li>
* <li>paletteSupported = unsupported</li>
* <li>transparency = supported</li>
* </ol>
*/
private static MapProducerCapabilities CAPABILITIES= new MapProducerCapabilities(false, false, false, true, null);
private final WMS wms;
public SVGBatikMapOutputFormat(WMS wms) {
this.wms = wms;
}
/**
* @return {@code ["image/svg+xml", "image/svg xml", "image/svg"]}
* @see org.geoserver.wms.GetMapOutputFormat#getOutputFormatNames()
*/
public Set<String> getOutputFormatNames() {
return SVG.OUTPUT_FORMATS;
}
/**
* @return {@code "image/svg+xml"}
* @see org.geoserver.wms.GetMapOutputFormat#getMimeType()
*/
public String getMimeType() {
return SVG.MIME_TYPE;
}
/**
*
* @see org.geoserver.wms.GetMapOutputFormat#produceMap(org.geoserver.wms.WMSMapContent)
*/
public BatikSVGMap produceMap(WMSMapContent mapContent) throws ServiceException, IOException {
StreamingRenderer renderer = setUpRenderer(mapContent);
SVGGraphics2D g = createSVGMap(renderer, mapContent);
renderer = null;
// This method of output does not output the DOCTYPE definiition
// TODO: make a config option that toggles wether doctype is
// written out.
// OutputFormat format = new OutputFormat();
// XMLSerializer serializer = new XMLSerializer(new OutputStreamWriter(out, "UTF-8"),
// format);
return new BatikSVGMap(mapContent, g);
}
private StreamingRenderer setUpRenderer(WMSMapContent mapContent) {
StreamingRenderer renderer;
renderer = new StreamingRenderer();
// optimized data loading was not here, but yet it seems sensible to
// have it...
Map<String, Object> rendererParams = new HashMap<String, Object>();
rendererParams.put("optimizedDataLoadingEnabled", Boolean.TRUE);
// we need the renderer to draw everything on the batik provided graphics object
rendererParams.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.FALSE);
// render everything in vector form if possible
rendererParams.put(StreamingRenderer.VECTOR_RENDERING_KEY, Boolean.TRUE);
rendererParams.put("renderingBuffer", new Integer(mapContent.getBuffer()));
if (DefaultWebMapService.isLineWidthOptimizationEnabled()) {
rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, true);
}
rendererParams.put(StreamingRenderer.SCALE_COMPUTATION_METHOD_KEY,
mapContent.getRendererScaleMethod());
renderer.setRendererHints(rendererParams);
renderer.setMapContent(mapContent);
return renderer;
}
public SVGGraphics2D createSVGMap(final StreamingRenderer renderer,
final WMSMapContent mapContent) throws ServiceException, IOException {
try {
MapContent map = renderer.getMapContent();
double width = -1;
double height = -1;
if (map instanceof WMSMapContent) {
WMSMapContent wmsMap = (WMSMapContent) map;
width = wmsMap.getMapWidth();
height = wmsMap.getMapHeight();
} else {
// get fromt he map content
Rectangle screenArea = map.getViewport().getScreenArea();
width = screenArea.getWidth();
height = screenArea.getHeight();
}
if ((height == -1) || (width == -1)) {
throw new IOException("Could not determine map dimensions");
}
SVGGeneratorContext context = setupContext();
SVGGraphics2D g = new SVGGraphics2D(context, true);
g.setSVGCanvasSize(new Dimension((int) width, (int) height));
// turn off/on anti aliasing
if (wms.isSvgAntiAlias()) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
} else {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
// enforce no more than x rendering errors
int maxErrors = wms.getMaxRenderingErrors();
MaxErrorEnforcer errorChecker = new MaxErrorEnforcer(renderer, maxErrors);
// Add a render listener that ignores well known rendering exceptions and reports back
// non
// ignorable ones
final RenderExceptionStrategy nonIgnorableExceptionListener;
nonIgnorableExceptionListener = new RenderExceptionStrategy(renderer);
renderer.addRenderListener(nonIgnorableExceptionListener);
renderer.paint(g, new Rectangle(g.getSVGCanvasSize()), mapContent.getRenderingArea(),
mapContent.getRenderingTransform());
// check if too many errors occurred
if (errorChecker.exceedsMaxErrors()) {
throw new ServiceException("More than " + maxErrors
+ " rendering errors occurred, bailing out.",
errorChecker.getLastException(), "internalError");
}
// check if a non ignorable error occurred
if (nonIgnorableExceptionListener.exceptionOccurred()) {
Exception renderError = nonIgnorableExceptionListener.getException();
throw new ServiceException("Rendering process failed", renderError, "internalError");
}
return g;
} catch (ParserConfigurationException e) {
throw new ServiceException("Unexpected exception", e, "internalError");
}
}
private SVGGeneratorContext setupContext() throws FactoryConfigurationError,
ParserConfigurationException {
Document document = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
// Create an instance of org.w3c.dom.Document
String svgNamespaceURI = "http://www.w3.org/2000/svg";
document = db.getDOMImplementation().createDocument(svgNamespaceURI, "svg", null);
// Set up the context
return SVGGeneratorContext.createDefault(document);
}
public MapProducerCapabilities getCapabilities(String format) {
return CAPABILITIES;
}
}