uk.ac.rdg.resc.edal.wms.ScreenshotServlet Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2013 The University of Reading
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University of Reading, nor the names of the
* authors or contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package uk.ac.rdg.resc.edal.wms;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.rdg.resc.edal.exceptions.EdalException;
/**
* Implementation of a servlet which generates images suitable for use in
* publications. These are aimed at being an improvement over simply taking a
* screenshot of a WMS client.
*
* The parameters this request accepts are (optional unless specified):
*
* image: true/false. Whether to generate an image or just an HTML page
* containing the image. This is generally not included.
*
* baseUrl: The WMS URL of the background map
*
* baseLayers: The layers to display from the background map
*
* bbox: The bounding box defining the viewport (mandatory)
*
* crs: The co-ordinate reference system code (mandatory)
*
* mapHeight: The desired height of the map
*
* mapWidth: The desired width of the map
*
* layerTitle: A comma-separated list of strings which make up the title. Each
* line represents the child of the NEXT one and is rendered as such. For
* example "TMP,FOAM one degree" will be similarly to:
*
* FOAM one degree > TMP
*
* elevation: A string representing the elevation
*
* time: A string representing the time
*
* server: The URL of the WMS server
*
* layer: The WMS layer name of the layer on the above server
*
* style: The base name of the style
*
* palette: The name of the palette to use
*
* scaleRange: Comma-separated colour scale range
*
* numColorBands: The number of colour bands to use
*
* targetElevation: The target elevation (for in-situ data)
*
* targetTime: The target time (for in-situ data)
*
* TODO Error handling is not implemented very well
*/
public class ScreenshotServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(ScreenshotServlet.class);
/**
* Handles a GET request. This will generate a screenshot and write it to
* the output stream, assuming
*
* This should be mapped to the correct URL in the web.xml
*
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fullRequest = request.getRequestURL().toString();
String servletUrl = fullRequest.substring(0,
fullRequest.indexOf(request.getServletPath()) + 1);
RequestParams params = new RequestParams(request.getParameterMap());
if (params.getString("image") != null && params.getString("image").equalsIgnoreCase("true")) {
BufferedImage image;
try {
image = drawScreenshot(params, servletUrl);
response.setContentType("image/png");
OutputStream output = response.getOutputStream();
ImageIO.write(image, "png", output);
} catch (EdalException e) {
/*
* TODO Handle this properly - maybe you should look at
* different Velocity template methods.
*
* Perhaps this should return an image explaining there is a
* problem?
*/
e.printStackTrace();
log.error("Problem with generating screenshot", e);
}
} else {
PrintWriter out = response.getWriter();
String url = request.getRequestURL() + "?" + request.getQueryString() + "&image=true";
out.println("To save the image, right click and select \"Save As\"
"
+ "Note: The image may take a long time to appear, depending on the speed of the data servers
"
+ "");
}
}
private BufferedImage drawScreenshot(RequestParams params, String servletUrl)
throws EdalException, IOException {
/*
* Retrieve some URL parameters and store for later
*/
String baseLayerUrl = params.getString("baseUrl");
String baseLayerNames = params.getMandatoryString("baseLayers");
if (baseLayerNames != null) {
baseLayerUrl += "&LAYERS=" + baseLayerNames;
}
String[] bboxStr = params.getString("bbox").split(",");
Float minLon = Float.parseFloat(bboxStr[0]);
Float maxLon = Float.parseFloat(bboxStr[2]);
Float minLat = Float.parseFloat(bboxStr[1]);
Float maxLat = Float.parseFloat(bboxStr[3]);
Float lonRange = maxLon - minLon;
String crs = params.getString("crs");
/*
* Set the background map URL
*
* TODO Replace these with a more reliable method (i.e. keep
* geo-referenced images locally for this purpose...)
*/
if (crs.equalsIgnoreCase("EPSG:5041")) {
baseLayerUrl = "http://godiva.rdg.ac.uk/geoserver/ReSC/wms?LAYERS=bluemarble-np";
} else if (crs.equalsIgnoreCase("EPSG:5042")) {
baseLayerUrl = "http://godiva.rdg.ac.uk/geoserver/ReSC/wms?LAYERS=bluemarble-sp";
} else {
baseLayerUrl = "http://godiva.rdg.ac.uk/geoserver/ReSC/wms?LAYERS=bluemarble";
}
int mapHeight = params.getPositiveInt("mapHeight", 384);
int mapWidth = params.getPositiveInt("mapWidth", 512);
String time = params.getString("time");
/*
* Get the legend first so that we know how big the final image needs to
* be
*/
BufferedImage colorBar = null;
URL url = createWmsUrl(params, false, minLon, minLat, maxLon, maxLat, 50, mapHeight,
servletUrl, time, true);
if (url != null) {
InputStream in = null;
try {
URLConnection conn = url.openConnection();
in = conn.getInputStream();
colorBar = ImageIO.read(in);
} finally {
if (in != null)
in.close();
}
}
/*
* Calculate the space needed for text above the image
*/
int textSpace = 25;
if (params.getString("layerTitle") != null) {
String[] titleElements = params.getString("layerTitle").split(",");
textSpace += 15 * titleElements.length;
}
if (time != null) {
textSpace += 20;
}
if (params.getString("elevation") != null) {
textSpace += 20;
}
/*
* Calculate the total size needed for the image
*/
int totalWidth = mapWidth + colorBar.getWidth() + 20;
int totalHeight;
if (mapHeight > colorBar.getHeight()) {
totalHeight = mapHeight + textSpace;
} else {
totalHeight = colorBar.getHeight() + textSpace;
}
/*
* Create the final image, fill it white, and set some rendering
* defaults
*/
BufferedImage image = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setBackground(Color.white);
/* Draw labels on the image for the layer name, time, elevation */
Font font = new Font("SansSerif", Font.BOLD, 16);
g.setPaint(Color.black);
g.setFont(font);
int vPos = 20;
int hPos = 10;
String title = params.getString("layerTitle");
if (title != null) {
int indent = 0;
String[] titleElements = title.split(",");
if (titleElements.length >= 2) {
for (int i = titleElements.length - 1; i > 0; i--) {
g.drawString(titleElements[i], hPos + indent, vPos);
vPos += 15;
g.drawString("\u21b3", hPos + indent + 8, vPos - 2);
indent += 20;
font = new Font("SansSerif", Font.BOLD, 14);
g.setFont(font);
}
}
if (titleElements.length > 0) {
g.drawString(titleElements[0], hPos + indent, vPos);
vPos += 15;
}
}
vPos += 5;
font = new Font("SansSerif", Font.BOLD, 14);
g.setFont(font);
if (time != null) {
g.drawString("Time: " + time, hPos, vPos);
vPos += 20;
}
String el = params.getString("elevation");
if (el != null) {
String depth = el;
String u = params.getString("zUnits");
String units = "";
if (u != null)
units = u;
if (depth.startsWith("-"))
g.drawString("Depth: " + el.substring(1) + units, hPos, vPos);
else
g.drawString("Elevation: " + el + units, hPos, vPos);
vPos += 30;
}
/*
* Text drawing done.
*
* Now draw the background image
*/
if ((!crs.equalsIgnoreCase("EPSG:4326") && !crs.equalsIgnoreCase("CRS:84"))
|| (minLon >= -180 && maxLon <= 180)) {
BufferedImage im = getImage(params, minLon, minLat, maxLon, maxLat, mapWidth,
mapHeight, baseLayerUrl, time);
g.drawImage(im, 0, textSpace, null);
} else if (minLon < -180 && maxLon <= 180) {
int lefWidth = (int) (mapWidth * (-180 - minLon) / (lonRange));
BufferedImage im = getImage(params, minLon + 360, minLat, 180f, maxLat, lefWidth,
mapHeight, baseLayerUrl, time);
g.drawImage(im, 0, textSpace, null);
im = getImage(params, -180f, minLat, maxLon, maxLat, mapWidth - lefWidth, mapHeight,
baseLayerUrl, time);
g.drawImage(im, lefWidth, textSpace, null);
} else if (minLon >= -180 && maxLon > 180) {
int rightWidth = (int) (mapWidth * (maxLon - 180f) / (lonRange));
BufferedImage im = getImage(params, minLon, minLat, 180f, maxLat,
mapWidth - rightWidth, mapHeight, baseLayerUrl, time);
g.drawImage(im, 0, textSpace, null);
im = getImage(params, -180f, minLat, maxLon - 360, maxLat, rightWidth, mapHeight,
baseLayerUrl, time);
g.drawImage(im, mapWidth - rightWidth, textSpace, null);
} else if (minLon < -180 && maxLon > 180) {
int leftWidth = (int) (mapWidth * (-180 - minLon) / (lonRange));
BufferedImage im = getImage(params, minLon + 360, minLat, 180f, maxLat, leftWidth,
mapHeight, baseLayerUrl, time);
g.drawImage(im, 0, textSpace, null);
int rightWidth = (int) (mapWidth * (maxLon - 180f) / (lonRange));
im = getImage(params, -180f, minLat, maxLon - 360, maxLat, rightWidth, mapHeight,
baseLayerUrl, time);
g.drawImage(im, mapWidth - rightWidth, textSpace, null);
im = getImage(params, -180f, minLat, 180f, maxLat, mapWidth - leftWidth - rightWidth,
mapHeight, baseLayerUrl, time);
g.drawImage(im, leftWidth, textSpace, null);
}
url = createWmsUrl(params, false, minLon, minLat, maxLon, maxLat, mapWidth, mapHeight,
servletUrl, time, false);
BufferedImage wmsLayer;
if (url != null) {
wmsLayer = ImageIO.read(url);
g.drawImage(wmsLayer, 0, textSpace, null);
}
if (colorBar != null) {
g.drawImage(colorBar, mapWidth + 10,
textSpace + (mapHeight - colorBar.getHeight()) / 2,
mapWidth + 10 + colorBar.getWidth(),
textSpace + (mapHeight + colorBar.getHeight()) / 2, 0, 0, colorBar.getWidth(),
colorBar.getHeight(), null);
}
return image;
}
private BufferedImage getImage(RequestParams params, Float minLon, Float minLat, Float maxLon,
Float maxLat, int width, int height, String bgUrl, String time) throws IOException {
BufferedImage image = null;
URL baseUrl = createWmsUrl(params, true, minLon, minLat, maxLon, maxLat, width, height,
bgUrl, time, false);
try {
image = ImageIO.read(baseUrl);
} catch (Exception e) {
e.printStackTrace();
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
return image;
}
private URL createWmsUrl(RequestParams params, boolean baseLayer, Float minLon, Float minLat,
Float maxLon, Float maxLat, int width, int height, String baseWmsUrl, String time,
boolean colorbar) throws UnsupportedEncodingException {
StringBuilder url = new StringBuilder();
if (baseLayer) {
Pattern p = Pattern.compile(" ");
if (baseWmsUrl != null) {
Matcher m = p.matcher(baseWmsUrl);
if (m != null)
baseWmsUrl = m.replaceAll("%20");
}
url.append(baseWmsUrl);
url.append("&STYLES=");
} else {
String server = params.getString("server");
if (server == null || server.equalsIgnoreCase("null")) {
/*
* If we don't have a server, we can't plot a layer
*/
return null;
}
url.append(server + "?SERVICE=WMS&LAYERS=" + params.getString("layer"));
String style = params.getString("style");
String palette = params.getString("palette");
if (style != null && palette != null) {
url.append("&STYLES=" + style + "/" + palette);
} else {
url.append("&STYLES=");
}
String scaleRange = params.getString("scaleRange");
if (scaleRange != null)
url.append("&COLORSCALERANGE=" + scaleRange);
String aboveMaxColor = params.getString("aboveMaxColor");
if (aboveMaxColor != null)
url.append("&ABOVEMAXCOLOR=" + URLEncoder.encode(aboveMaxColor, "UTF-8"));
String belowMinColor = params.getString("belowMinColor");
if (belowMinColor != null)
url.append("&BELOWMINCOLOR=" + URLEncoder.encode(belowMinColor, "UTF-8"));
String noDataColor = params.getString("noDataColor");
if (noDataColor != null)
url.append("&BGCOLOR=" + URLEncoder.encode(noDataColor, "UTF-8"));
String numColorBands = params.getString("numColorBands");
if (numColorBands != null)
url.append("&NUMCOLORBANDS=" + numColorBands);
String logscale = params.getString("logscale");
if (logscale != null)
url.append("&LOGSCALE=" + logscale);
if (time != null)
/*
* Yuck. But it seems to be necessary, otherwise "+" gets
* interpreted wrongly
*/
url.append("&TIME=" + URLEncoder.encode(time, "UTF-8"));
String elevation = params.getString("elevation");
if (elevation != null)
url.append("&ELEVATION=" + elevation);
String targetElevation = params.getString("targetelevation");
if (targetElevation != null)
url.append("&TARGETELEVATION=" + targetElevation);
String targetTime = params.getString("targettime");
if (targetTime != null)
/*
* Yuck. But it seems to be necessary, otherwise "+" gets
* interpreted wrongly
*/
url.append("&TARGETTIME="
+ URLEncoder.encode(targetTime, "UTF-8"));
}
url.append("&TRANSPARENT=true");
url.append("&VERSION=1.1.1&SERVICE=WMS&REQUEST=");
if (colorbar) {
url.append("GetLegendGraphic");
} else {
/*
* Don't specify a width for a colourbar. A different default will
* be used depending on whether we have a 1D or 2D variable
*/
url.append("GetMap&WIDTH=" + width);
}
url.append("&FORMAT=image/png&HEIGHT=" + height);
url.append("&BBOX=" + minLon + "," + minLat + "," + maxLon + "," + maxLat);
String crs = params.getString("crs");
if (crs.equalsIgnoreCase("CRS:84")) {
crs = "EPSG:4326";
}
url.append("&SRS=" + crs);
try {
return new URL(url.toString().replaceAll(" ", "%20"));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy