package org.mapfish.print.processor.map;
import com.codahale.metrics.MetricRegistry;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.Closer;
import com.google.common.io.Files;
import com.lowagie.text.BadElementException;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfWriter;
import com.vividsolutions.jts.awt.ShapeWriter;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Polygon;
import jsr166y.ForkJoinPool;
import net.sf.jasperreports.engine.JRException;
import org.apache.batik.svggen.DefaultStyleHandler;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.transcoder.TranscoderException;
import org.geotools.data.FeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.mapfish.print.Constants;
import org.mapfish.print.ExceptionUtils;
import org.mapfish.print.SvgUtil;
import org.mapfish.print.attribute.map.AreaOfInterest;
import org.mapfish.print.attribute.map.BBoxMapBounds;
import org.mapfish.print.attribute.map.GenericMapAttribute.GenericMapAttributeValues;
import org.mapfish.print.attribute.map.MapAttribute.MapAttributeValues;
import org.mapfish.print.attribute.map.MapBounds;
import org.mapfish.print.attribute.map.MapLayer;
import org.mapfish.print.attribute.map.MapLayer.RenderType;
import org.mapfish.print.attribute.map.MapfishMapContext;
import org.mapfish.print.attribute.map.ZoomLevelSnapStrategy;
import org.mapfish.print.attribute.map.ZoomToFeatures.ZoomType;
import org.mapfish.print.config.Configuration;
import org.mapfish.print.config.ConfigurationException;
import org.mapfish.print.http.HttpRequestCache;
import org.mapfish.print.http.MfClientHttpRequestFactory;
import org.mapfish.print.map.Scale;
import org.mapfish.print.map.geotools.AbstractFeatureSourceLayer;
import org.mapfish.print.map.geotools.FeatureLayer;
import org.mapfish.print.map.geotools.grid.GridLayer;
import org.mapfish.print.parser.HasDefaultValue;
import org.mapfish.print.processor.AbstractProcessor;
import org.mapfish.print.processor.InputOutputValue;
import org.mapfish.print.processor.InternalValue;
import org.mapfish.print.processor.http.MfClientHttpRequestFactoryProvider;
import org.mapfish.print.processor.jasper.ImagesSubReport;
import org.mapfish.print.processor.jasper.JasperReportBuilder;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import static org.geotools.renderer.lite.RendererUtilities.worldToScreenTransform;
import static org.mapfish.print.Constants.PDF_DPI;
/**
* <p>A processor to render a map that can be embedded as a sub-report into a JasperReports
* template.</p>
* <p>See also: <a href="attributes.html#!map">!map</a> attribute</p>
* [[examples=verboseExample]]
*/
public final class CreateMapProcessor extends AbstractProcessor<CreateMapProcessor.Input, CreateMapProcessor.Output> {
private static final Logger LOGGER = LoggerFactory.getLogger(CreateMapProcessor.class);
enum BufferedImageType {
TYPE_4BYTE_ABGR(BufferedImage.TYPE_4BYTE_ABGR, true),
TYPE_4BYTE_ABGR_PRE(BufferedImage.TYPE_4BYTE_ABGR_PRE, true),
TYPE_3BYTE_BGR(BufferedImage.TYPE_3BYTE_BGR, false),
TYPE_BYTE_BINARY(BufferedImage.TYPE_BYTE_BINARY, false),
TYPE_BYTE_GRAY(BufferedImage.TYPE_BYTE_GRAY, false),
TYPE_BYTE_INDEXED(BufferedImage.TYPE_BYTE_INDEXED, false),
TYPE_INT_BGR(BufferedImage.TYPE_INT_BGR, false),
TYPE_INT_RGB(BufferedImage.TYPE_INT_RGB, false),
TYPE_INT_ARGB(BufferedImage.TYPE_INT_ARGB, true),
TYPE_INT_ARGB_PRE(BufferedImage.TYPE_INT_ARGB_PRE, true),
TYPE_USHORT_555_RGB(BufferedImage.TYPE_USHORT_555_RGB, false),
TYPE_USHORT_565_RGB(BufferedImage.TYPE_USHORT_565_RGB, false),
TYPE_USHORT_GRAY(BufferedImage.TYPE_USHORT_GRAY, false);
private final int value;
private final boolean transparency;
BufferedImageType(final int value, final boolean transparency) {
this.value = value;
this.transparency = transparency;
}
static BufferedImageType lookupValue(final String name) {
for (BufferedImageType bufferedImageType : values()) {
if (bufferedImageType.name().equalsIgnoreCase(name)) {
return bufferedImageType;
}
}
throw new IllegalArgumentException("'" + name + "is not a recognized " + BufferedImageType.class.getName() + " enum value");
}
}
@Autowired
FeatureLayer.Plugin featureLayerPlugin;
@Autowired
private MetricRegistry metricRegistry;
@Resource(name = "requestForkJoinPool")
private ForkJoinPool requestForkJoinPool;
private BufferedImageType imageType = BufferedImageType.TYPE_4BYTE_ABGR;
private BufferedImageType jpegImageType = BufferedImageType.TYPE_3BYTE_BGR;
/**
* Constructor.
*/
protected CreateMapProcessor() {
super(Output.class);
}
@Override
public Input createInputParameter() {
return new Input();
}
@Override
public Output execute(final Input param, final ExecutionContext context) throws Exception {
checkCancelState(context);
MapAttributeValues mapValues = (MapAttributeValues) param.map;
if (mapValues.zoomToFeatures != null) {
zoomToFeatures(param.clientHttpRequestFactoryProvider.get(), mapValues, context);
}
final MapfishMapContext mapContext = createMapContext(mapValues);
final List<URI> graphics = createLayerGraphics(
param.tempTaskDirectory,
param.clientHttpRequestFactoryProvider.get(),
mapValues, context, mapContext);
checkCancelState(context);
final URI mapSubReport;
if (param.map.getTemplate().isMapExport()) {
mapSubReport = createMergedGraphic(param.tempTaskDirectory, graphics, mapContext, param.outputFormat);
} else {
mapSubReport = createMapSubReport(param.tempTaskDirectory, mapValues.getMapSize(), graphics, mapValues.getDpi());
}
return new Output(graphics, mapSubReport.toString(), mapContext);
}
@Override
protected void extraValidation(final List<Throwable> validationErrors, final Configuration configuration) {
if (this.imageType == null) {
validationErrors.add(new ConfigurationException("No imageType defined in " + getClass().getName()));
}
}
private URI createMergedGraphic(final File printDirectory,
final List<URI> graphics,
final MapfishMapContext mapContext,
final String outputFormat) throws IOException, JRException {
final File mergedGraphic = File.createTempFile("map-", "." + outputFormat, printDirectory);
int width = Math.round(mapContext.getMapSize().width);
int height = Math.round(mapContext.getMapSize().height);
if ("pdf".equalsIgnoreCase(outputFormat)) {
com.lowagie.text.Document document = new com.lowagie.text.Document(
new com.lowagie.text.Rectangle(width, height));
try {
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(mergedGraphic));
document.open();
PdfContentByte pdfCB = writer.getDirectContent();
Graphics g = pdfCB.createGraphics(width, height);
try {
drawGraphics(width, height, graphics, g);
} finally {
g.dispose();
}
} catch (BadElementException e) {
throw new IOException(e);
} catch (DocumentException e) {
throw new IOException(e);
} finally {
document.close();
}
} else {
boolean isJpeg = RenderType.fromFileExtension(outputFormat) == RenderType.JPEG;
final BufferedImage bufferedImage = new BufferedImage(width, height,
(isJpeg ? this.jpegImageType.value : this.imageType.value));
Graphics g = bufferedImage.getGraphics();
if (isJpeg) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
}
try {
drawGraphics(width, height, graphics, g);
} finally {
g.dispose();
}
ImageIO.write(bufferedImage, outputFormat, mergedGraphic);
}
return mergedGraphic.toURI();
}
private void drawGraphics(final int width, final int height,
final List<URI> graphics, final Graphics g) throws IOException, JRException {
for (URI graphic : graphics) {
final File graphicFile = new File(graphic);
if (Files.getFileExtension(graphicFile.getName()).equals("svg")) {
try {
g.drawImage(SvgUtil.convertFromSvg(graphic, width, height), 0, 0, width, height, null);
} catch (TranscoderException e) {
throw new IllegalStateException(e);
}
} else {
BufferedImage image = ImageIO.read(graphicFile);
g.drawImage(image, 0, 0, width, height, null);
}
}
}
private URI createMapSubReport(final File printDirectory,
final Dimension mapSize,
final List<URI> graphics,
final double dpi) throws IOException, JRException {
final ImagesSubReport subReport = new ImagesSubReport(graphics, mapSize, dpi);
final File compiledReport = File.createTempFile("map-",
JasperReportBuilder.JASPER_REPORT_COMPILED_FILE_EXT, printDirectory);
subReport.compile(compiledReport);
return compiledReport.toURI();
}
private RenderType getSupportedRenderType(final RenderType renderType) {
if (renderType == RenderType.UNKNOWN || renderType == RenderType.TIFF) {
return RenderType.PNG;
} else {
return renderType;
}
}
private void warnIfDifferentRenderType(final RenderType renderType, final MapLayer layer) {
if (renderType != layer.getRenderType()) {
LOGGER.warn(
"Layer " + layer.getName() + " has " +
layer.getRenderType().toString() + " format, storing as PNG.");
}
}
private MapfishMapContext getTransformer(final MapfishMapContext mapContext, final double imageBufferScaling) {
return new MapfishMapContext(
mapContext,
mapContext.getBounds(),
new Dimension(
(int) Math.round(mapContext.getMapSize().width * imageBufferScaling),
(int) Math.round(mapContext.getMapSize().height * imageBufferScaling)
),
mapContext.getRotation(),
mapContext.getDPI(),
mapContext.isForceLongitudeFirst(),
mapContext.isDpiSensitiveStyle()
);
}
private List<URI> createLayerGraphics(final File printDirectory,
final MfClientHttpRequestFactory clientHttpRequestFactory,
final MapAttributeValues mapValues,
final ExecutionContext context,
final MapfishMapContext mapContext)
throws Exception {
// reverse layer list to draw from bottom to top. normally position 0 is top-most layer.
final List<MapLayer> layers = Lists.reverse(Lists.newArrayList(mapValues.getLayers()));
final AreaOfInterest areaOfInterest = addAreaOfInterestLayer(mapValues, layers);
final String mapKey = UUID.randomUUID().toString();
final List<URI> graphics = new ArrayList<URI>(layers.size());
HttpRequestCache cache = new HttpRequestCache(printDirectory, this.metricRegistry);
//prepare layers for rendering
for (final MapLayer layer : layers) {
layer.prepareRender(mapContext);
final MapfishMapContext transformer = getTransformer(mapContext,
layer.getImageBufferScaling());
layer.cacheResources(cache, clientHttpRequestFactory, transformer);
}
//now we download and cache all images at once
cache.cache(this.requestForkJoinPool);
for (int i = 0; i < layers.size(); i++) {
MapLayer layer = layers.get(i);
checkCancelState(context);
final File path;
RenderType renderType = getSupportedRenderType(layer.getRenderType());
if (layer.getRenderType() == RenderType.SVG) {
// render layer as SVG
final SVGGraphics2D graphics2D = getSvgGraphics(mapContext.getMapSize());
try {
Graphics2D clippedGraphics2D = createClippedGraphics(
mapContext, areaOfInterest, graphics2D);
layer.render(clippedGraphics2D, clientHttpRequestFactory, mapContext);
path = new File(printDirectory, mapKey + "_layer_" + i + ".svg");
saveSvgFile(graphics2D, path);
} finally {
graphics2D.dispose();
}
} else {
// render layer as raster graphic
warnIfDifferentRenderType(renderType, layer);
final double imageBufferScaling = layer.getImageBufferScaling();
final BufferedImageType layerImageType =
renderType == RenderType.JPEG ? this.jpegImageType : this.imageType;
final BufferedImage bufferedImage = new BufferedImage(
(int) Math.round(mapContext.getMapSize().width * imageBufferScaling),
(int) Math.round(mapContext.getMapSize().height * imageBufferScaling),
layerImageType.value
);
Graphics2D graphics2D = createClippedGraphics(
mapContext, areaOfInterest,
bufferedImage.createGraphics()
);
if (!layerImageType.transparency) {
// the image is opaque and therefore needs a white background
final Color prevColor = graphics2D.getColor();
graphics2D.setColor(Color.WHITE);
graphics2D.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
graphics2D.setColor(prevColor);
}
try {
MapfishMapContext transformer = getTransformer(mapContext, layer.getImageBufferScaling());
layer.render(graphics2D, clientHttpRequestFactory, transformer);
// Merge consecutive layers of same render type and same buffer scaling (native
// resolution)
while (
i < layers.size() - 1 &&
getSupportedRenderType(layers.get(i + 1).getRenderType()) == renderType &&
imageBufferScaling == layers.get(i + 1).getImageBufferScaling()
) {
layer = layers.get(++i);
checkCancelState(context);
layer.prepareRender(mapContext);
warnIfDifferentRenderType(renderType, layer);
layer.render(graphics2D, clientHttpRequestFactory, transformer);
}
path = new File(
printDirectory,
mapKey + "_layer_" + i + "." + renderType.toString().toLowerCase());
ImageIO.write(bufferedImage, renderType.toString(), path);
} finally {
graphics2D.dispose();
}
}
graphics.add(path.toURI());
}
return graphics;
}
/**
* @param mapValues The map parameters.
* @return The map context.
*/
@VisibleForTesting
public static MapfishMapContext createMapContext(final MapAttributeValues mapValues) {
final Dimension mapSize = mapValues.getMapSize();
Rectangle paintArea = new Rectangle(mapSize);
final double dpi = mapValues.getDpi();
MapBounds bounds = mapValues.getMapBounds();
bounds = adjustBoundsToScaleAndMapSize(mapValues, paintArea, bounds, dpi);
// if the DPI is higher than the PDF DPI we need to make the image larger so the image put in the PDF is large enough for the
// higher DPI printer
final double dpiRatio = dpi / PDF_DPI;
paintArea.setBounds(0, 0,
(int) Math.ceil(mapSize.getWidth() * dpiRatio),
(int) Math.ceil(mapSize.getHeight() * dpiRatio));
return new MapfishMapContext(bounds, paintArea.getSize(), mapValues.getRotation(), dpi,
mapValues.longitudeFirst, mapValues.isDpiSensitiveStyle());
}
private AreaOfInterest addAreaOfInterestLayer(
@Nonnull final MapAttributeValues mapValues,
@Nonnull final List<MapLayer> layers) throws IOException {
final AreaOfInterest areaOfInterest = mapValues.areaOfInterest;
if (areaOfInterest != null && areaOfInterest.display == AreaOfInterest.AoiDisplay.RENDER) {
FeatureLayer.FeatureLayerParam param = new FeatureLayer.FeatureLayerParam();
param.defaultStyle = Constants.Style.OverviewMap.NAME + ":" + areaOfInterest.getArea().getClass().getSimpleName();
param.style = areaOfInterest.style;
param.renderAsSvg = areaOfInterest.renderAsSvg;
param.features = areaOfInterest.areaToFeatureCollection(mapValues);
final FeatureLayer featureLayer = this.featureLayerPlugin.parse(mapValues.getTemplate(), param);
layers.add(featureLayer);
}
return areaOfInterest;
}
private Graphics2D createClippedGraphics(
@Nonnull final MapfishMapContext transformer,
@Nullable final AreaOfInterest areaOfInterest,
@Nonnull final Graphics2D graphics2D
) {
if (areaOfInterest != null && areaOfInterest.display == AreaOfInterest.AoiDisplay.CLIP) {
final Polygon screenGeometry = areaOfInterestInScreenCRS(transformer, areaOfInterest);
final ShapeWriter shapeWriter = new ShapeWriter();
final Shape clipShape = shapeWriter.toShape(screenGeometry);
return new ConstantClipGraphics2D(graphics2D, clipShape);
}
return graphics2D;
}
private Polygon areaOfInterestInScreenCRS(
@Nonnull final MapfishMapContext transformer,
@Nullable final AreaOfInterest areaOfInterest) {
if (areaOfInterest != null) {
final AffineTransform worldToScreenTransform = worldToScreenTransform(
transformer.toReferencedEnvelope(),
transformer.getPaintArea()
);
MathTransform mathTransform = new AffineTransform2D(worldToScreenTransform);
final Polygon screenGeometry;
try {
screenGeometry = (Polygon) JTS.transform(areaOfInterest.getArea(), mathTransform);
} catch (TransformException e) {
throw new RuntimeException(e);
}
return screenGeometry;
}
return null;
}
/**
* If requested, adjust the bounds to the nearest scale and the map size.
*
* @param mapValues Map parameters.
* @param paintArea The size of the painting area.
* @param bounds The map bounds.
* @param dpi the DPI.
*/
public static MapBounds adjustBoundsToScaleAndMapSize(
final GenericMapAttributeValues mapValues,
final Rectangle paintArea,
final MapBounds bounds,
final double dpi) {
MapBounds newBounds = bounds;
if (mapValues.isUseNearestScale()) {
newBounds = newBounds.adjustBoundsToNearestScale(
mapValues.getZoomLevels(),
mapValues.getZoomSnapTolerance(),
mapValues.getZoomLevelSnapStrategy(),
mapValues.getZoomSnapGeodetic(),
paintArea, dpi);
}
newBounds = new BBoxMapBounds(newBounds.toReferencedEnvelope(paintArea));
if (mapValues.isUseAdjustBounds()) {
newBounds = newBounds.adjustedEnvelope(paintArea);
}
return newBounds;
}
/**
* Create a SVG graphic with the give dimensions.
*
* @param size The size of the SVG graphic.
*/
public static SVGGraphics2D getSvgGraphics(final Dimension size)
throws ParserConfigurationException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.getDOMImplementation().createDocument(null, "svg", null);
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
ctx.setStyleHandler(new OpacityAdjustingStyleHandler());
ctx.setComment("Generated by GeoTools2 with Batik SVG Generator");
SVGGraphics2D g2d = new SVGGraphics2D(ctx, true);
g2d.setSVGCanvasSize(size);
return g2d;
}
/**
* Save a SVG graphic to the given path.
*
* @param graphics2d The SVG graphic to save.
* @param path The file.
*/
public static void saveSvgFile(final SVGGraphics2D graphics2d, final File path) throws IOException {
Closer closer = Closer.create();
try {
final FileOutputStream fs = closer.register(new FileOutputStream(path));
final OutputStreamWriter outputStreamWriter = closer.register(new OutputStreamWriter(fs, "UTF-8"));
Writer osw = closer.register(new BufferedWriter(outputStreamWriter));
graphics2d.stream(osw, true);
} finally {
closer.close();
}
}
private void zoomToFeatures(
final MfClientHttpRequestFactory clientHttpRequestFactory,
final MapAttributeValues mapValues,
final ExecutionContext context) {
ReferencedEnvelope bounds = getFeatureBounds(clientHttpRequestFactory,
mapValues, context);
if (bounds != null) {
if (mapValues.zoomToFeatures.zoomType == ZoomType.CENTER) {
// center the map on the center of the feature bounds
Coordinate center = bounds.centre();
mapValues.center = new double[] {center.x, center.y};
if (mapValues.zoomToFeatures.minScale != null) {
LOGGER.warn(
"The map.zoomToFeatures.minScale is deprecated, " +
"please use directly the map.scale");
mapValues.scale = mapValues.zoomToFeatures.minScale;
}
mapValues.recalculateBounds();
} else if (mapValues.zoomToFeatures.zoomType == ZoomType.EXTENT) {
if (bounds.getWidth() == 0.0 && bounds.getHeight() == 0.0) {
// single point, so we directly set the center
Coordinate center = bounds.centre();
mapValues.center = new double[] {center.x, center.y};
mapValues.bbox = null;
mapValues.scale = mapValues.zoomToFeatures.minScale;
mapValues.recalculateBounds();
} else {
mapValues.bbox = new double[] {
bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()};
mapValues.center = null;
mapValues.recalculateBounds();
MapBounds mapBounds = mapValues.getMapBounds();
Rectangle paintArea = new Rectangle(mapValues.getMapSize());
// expand the bounds so that they match the ratio of the paint area
mapBounds = mapBounds.adjustedEnvelope(paintArea);
if (mapValues.zoomToFeatures.minMargin != null) {
// add a margin around the feature bounds
mapBounds = ((BBoxMapBounds) mapBounds).expand(mapValues.zoomToFeatures.minMargin, paintArea);
}
final Scale scale = mapBounds.getScale(paintArea, mapValues.getDpi());
final Scale minScale = new Scale(mapValues.zoomToFeatures.minScale,
mapValues.getMapBounds().getProjection(),
mapValues.getDpi());
if (scale.getResolution() < minScale.getResolution()) {
// if the current scale is smaller than the min. scale, change it
mapBounds = mapBounds.zoomToScale(minScale);
}
if (mapValues.isUseNearestScale()) {
// if fixed scales are used, take next higher scale
mapBounds = mapBounds.adjustBoundsToNearestScale(
mapValues.getZoomLevels(), 0.0,
ZoomLevelSnapStrategy.HIGHER_SCALE,
mapValues.getZoomSnapGeodetic(),
paintArea, mapValues.getDpi());
}
mapValues.setMapBounds(mapBounds);
}
}
}
}
/**
* Get the bounding-box containing all features of all layers.
*/
private ReferencedEnvelope getFeatureBounds(
final MfClientHttpRequestFactory clientHttpRequestFactory,
final MapAttributeValues mapValues, final ExecutionContext context) {
final MapfishMapContext mapContext = createMapContext(mapValues);
String layerName = mapValues.zoomToFeatures.layer;
ReferencedEnvelope bounds = null;
for (MapLayer layer : mapValues.getLayers()) {
checkCancelState(context);
if ((!Strings.isNullOrEmpty(layerName) && layerName.equals(layer.getName())) ||
(Strings.isNullOrEmpty(layerName) && layer instanceof AbstractFeatureSourceLayer &&
!(layer instanceof GridLayer))) {
AbstractFeatureSourceLayer featureLayer = (AbstractFeatureSourceLayer) layer;
FeatureSource<?, ?> featureSource = featureLayer.getFeatureSource(clientHttpRequestFactory, mapContext);
FeatureCollection<?, ?> features;
try {
features = featureSource.getFeatures();
} catch (IOException e) {
throw ExceptionUtils.getRuntimeException(e);
}
if (!features.isEmpty()) {
if (bounds == null) {
bounds = features.getBounds();
} else {
bounds.expandToInclude(features.getBounds());
}
}
}
}
return bounds;
}
/**
* Set the type of buffered image rendered to. See {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType}.
* <p></p>
* Default is {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType#TYPE_4BYTE_ABGR}.
*
* @param imageType one of the {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType} values.
*/
public void setImageType(final String imageType) {
this.imageType = BufferedImageType.lookupValue(imageType);
}
/**
* Set the type of buffered image rendered to for JPEG files.
* See {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType}.
* <p></p>
* Default is {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType#TYPE_3BYTE_BGR}.
*
* @param jpegImageType one of the
* {@link org.mapfish.print.processor.map.CreateMapProcessor.BufferedImageType} values.
*/
public void setJpegImageType(final String jpegImageType) {
this.jpegImageType = BufferedImageType.lookupValue(jpegImageType);
}
/**
* The Input object for processor.
*/
public static class Input {
/**
* A factory for making http requests. This is added to the values by the framework and therefore
* does not need to be set in configuration
*/
public MfClientHttpRequestFactoryProvider clientHttpRequestFactoryProvider;
/**
* The required parameters for the map.
*/
// e.g. the grid layer will be self modified on drawing, and we needs the result for the overview map.
@InputOutputValue
public GenericMapAttributeValues map;
/**
* The path to the temporary directory for the print task.
*/
public File tempTaskDirectory;
/**
* The output format.
*/
@HasDefaultValue
public String outputFormat = null;
}
/**
* Output for the processor.
*/
public static final class Output {
/**
* The paths to a graphic for each layer.
*/
@InternalValue
public final List<URI> layerGraphics;
/**
* The path to the compiled sub-report for the map.
*/
public final String mapSubReport;
/**
* The map parameters used after zooming and all other calculations that are made.
*/
public final MapfishMapContext mapContext;
private Output(final List<URI> layerGraphics,
final String mapSubReport,
final MapfishMapContext mapContext) {
this.layerGraphics = layerGraphics;
this.mapSubReport = mapSubReport;
this.mapContext = mapContext;
}
}
private static final class OpacityAdjustingStyleHandler extends DefaultStyleHandler {
@Override
public void setStyle(final Element element,
final Map styleMap,
final SVGGeneratorContext generatorContext) {
String tagName = element.getTagName();
Iterator iter = styleMap.keySet().iterator();
while (iter.hasNext()) {
String styleName = (String) iter.next();
if (element.getAttributeNS(null, styleName).length() == 0) {
if (styleName.equals("opacity")) {
if (appliesTo(styleName, tagName)) {
element.setAttributeNS(null, "fill-opacity",
(String) styleMap.get(styleName));
element.setAttributeNS(null, "stroke-opacity",
(String) styleMap.get(styleName));
}
} else {
if (appliesTo(styleName, tagName)) {
element.setAttributeNS(null, styleName,
(String) styleMap.get(styleName));
}
}
}
}
}
}
}