package com.c2c.controller; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.text.AttributedString; import java.util.ArrayList; import java.util.Collection; import java.util.Formatter; import java.util.Locale; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.feature.visitor.MaxVisitor; import org.geotools.feature.visitor.MinVisitor; import org.geotools.styling.Style; import org.geotools.util.NullProgressListener; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.c2c.controller.Util.SYMBOL_TYPE; import com.c2c.data.DataQueryFeatureSource; import com.c2c.style.LegendDataGatherer; import com.c2c.style.Representation; import com.c2c.style.StyleGenerationParams; /** * The Controller for handling compute requests. * <p/> * This class is registered as a bean in ws-servlet.xml. * * @author yves, pmauduit */ @Controller @RequestMapping("/getlegend") public class GetLegend extends AbstractQueryingController { /** * padding percentage factor at both sides of the legend. */ public static final float hpaddingFactor = 0.15f; /** * top & bottom padding percentage factor for the legend */ public static final float vpaddingFactor = 0.15f; private static final Font FONT_NORMAL = new Font("SansSerif", Font.PLAIN, 10); private static final Font FONT_BOLD = new Font("SansSerif", Font.BOLD, 10); private static final Font FONT_ITALIC = new Font("SansSerif", Font.ITALIC, 10); private static final int N_PROP_CLASSES = 4; private class LegendSettings { public int getWidth() { return width; } public int getHeight() { return height; } public BufferedImage getChartSymbol() { return chartSymbol; } public BufferedImage getOverlayLegend() { return overlayLegend; } public String getChoropletIndicator() { return choropletIndicator; } public String getSymbolIndicator() { return symbolIndicator; } private final int width ; private final int height ; private final BufferedImage chartSymbol; private final BufferedImage overlayLegend; private final String choropletIndicator; private final String symbolIndicator; public LegendSettings(int _width, int _height, BufferedImage _chartSym, BufferedImage _overlayLgd, String _choropletIndicator, String _symbolIndicator) { width = _width; height = _height; chartSymbol = _chartSym; overlayLegend = _overlayLgd; choropletIndicator = _choropletIndicator; symbolIndicator = _symbolIndicator; } } @RequestMapping(method = RequestMethod.GET) public void getlegend(HttpServletRequest request, HttpServletResponse response, @RequestParam("QUERYID") String queryId, @RequestParam("STYLEID") String styleId, @RequestParam(value = "FORMAT", required = false) String format, @RequestParam(value = "FORMAT_OPTIONS", required = false) String format_options) throws Exception { if (format == null) { format = "image/png"; } DataQueryFeatureSource rs = getCache().getResults(queryId); SimpleFeatureSource results = (SimpleFeatureSource) rs.getFeatureSource(); response.setContentType(format); response.addHeader("Cache-Control", "no-cache"); response.addHeader("Expires", "-1"); StyleGenerationParams sld = getCache().getStyle(styleId); int dpi = 90; if (format_options != null) { dpi = Util.getDpiFromFormat(format_options); } LegendSettings lgds = prepareLegendSize(rs, sld, dpi); int width = lgds.getWidth(); int height = lgds.getHeight(); Rectangle imageSize = new Rectangle(width, height); renderLegend(response, results, imageSize, sld, lgds, parseFormat(format), dpi); } private String parseFormat(String format) { if (format == null) { return "png"; } String[] parts = format.split("/"); if (parts.length == 1) { return format; } return parts[1]; } private void renderLegend(HttpServletResponse response, SimpleFeatureSource results, Rectangle imageSize, StyleGenerationParams sld, LegendSettings lgds, String format, int dpi) throws Exception { double ratio = (double) dpi/90; BufferedImage image = new BufferedImage((int) (imageSize.width * ratio), (int) (imageSize.height * ratio), BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = image.createGraphics(); graphics.scale(ratio, ratio); Style sld2 = sld.getGtStyle(results.getFeatures()); LegendDataGatherer legendDataGatherer = new LegendDataGatherer(); sld2.accept(legendDataGatherer); paint(graphics, legendDataGatherer.getRepresentations(), sld, results, lgds); ServletOutputStream output = response.getOutputStream(); try { ImageIO.write(image, format, output); } finally { output.close(); graphics.dispose(); } } private LegendSettings prepareLegendSize(DataQueryFeatureSource rs, StyleGenerationParams sld, int dpi) { int height = 1; int width = 250; BufferedImage chartSymbol = null; BufferedImage overlayLegend = null; String choropletIndicator = null; String symbolIndicator = null; // choropleth if (sld.isChoroplethsEnabled()) { if (sld.getNbClasses() > 0) { //height += (48 + (30 + 5) * sld.getNbClasses()); // 25 px by nbclasses / number of results in the resultset height += (25 * sld.getNbClasses()); } else { /* count each lines then 10 * nblines */ try{ int nbRows = rs.getFeatureSource().getFeatures().size(); height += (25 * nbRows); } catch (IOException e) { // Unable to get the number of rows of // our resultset. Considering we got 5 lines. height += (25 * 5); } } String [] indicators = {sld.getChoroplethsIndicator()}; choropletIndicator = Util.getHumanReadableIndicators(rs, indicators)[0]; } if (sld.isOverlaySymbolsEnabled()) { // case 1 : prop. symbols ; no idea on how much // it would take ; let's say 10px margin top / bottom = max symbol size if (sld.getSymbolType() == Util.SYMBOL_TYPE.PROPORTIONAL_SYMBOLS) { height += (42 + sld.getMaxOverlaySize() + N_PROP_CLASSES * (sld.getMaxOverlaySize() + 10)); String [] indicators = sld.getOverlayIndicators(); symbolIndicator = Util.getHumanReadableIndicators(rs, indicators)[0]; } // case 2 : pies or bars // we are then prefetching the images we are going to use if ((sld.getSymbolType() == SYMBOL_TYPE.BARS) || (sld.getSymbolType() == SYMBOL_TYPE.PIES)) { symbolIndicator = Util.getSymbolsDimensions(rs); String type = sld.getSymbolType() == SYMBOL_TYPE.BARS ? "bar" : "pie"; String data = ""; // Create a URL for the image's location for (int i = 0 ; i < sld.getIndicatorsCount(); i++) { if (i==0) { data += String.format("%d", i+1); } else { data += String.format(",%d", i+1); } } try { int size = 55; URL url = new URL("http://localhost:8080/webbi/getoverlayicon?type=" + type + "&width=" + size + "&height=" + size + "&data="+data+"&legend=false"); URLConnection urlConn = url.openConnection(); // first "symbol" chartSymbol = ImageIO.read(urlConn.getInputStream()); int img_w = chartSymbol.getWidth(); int img_h = chartSymbol.getHeight(); height += (12 + img_h); if (width < img_w) { width = img_w; } // second one (actually the legend, generated by JFreeCharts) String [] lbls = sld.getOverlayIndicators(); lbls = Util.getHumanReadableIndicators(rs, sld.getOverlayIndicators()); String labels = ""; for (int i =0 ; i < lbls.length; i++) { if (i == 0) labels += lbls[i]; else labels += "," + lbls[i]; } labels = URLEncoder.encode(labels, "UTF-8"); url = new URL("http://localhost:8080/webbi/getoverlayicon?type="+type+"&width="+width+"&height=300&data="+data+"&labels="+labels+"&legend=true"); urlConn = url.openConnection(); // draw it on the legend overlayLegend = ImageIO.read(urlConn.getInputStream()); img_w = overlayLegend.getWidth(); img_h = overlayLegend.getHeight(); height += (12 + img_h); if (width < img_w) { width = img_w; } // we are losing 12 + 24 px somehow (by reading the paint() method) // adding a "security" margin of 12px. height += 48 ; } catch (Exception e) { e.printStackTrace(); } } } else{ /* 250px ought to be enough */ width = 250; } return new LegendSettings(width, height, chartSymbol, overlayLegend, choropletIndicator, symbolIndicator); } private void paint(Graphics2D graphics, Collection<Representation> representations, StyleGenerationParams sld, SimpleFeatureSource results, LegendSettings lgds) throws IOException { graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.setColor(Color.WHITE); graphics.setFont(FONT_NORMAL); graphics.setColor(Color.BLACK); int cur_v = 0, cur_h = 10; //position of current item in legend int box_w = 30, box_h = 20; //size of choropleth legend box in pixels int width = lgds.getWidth(); BufferedImage chartSymbol = lgds.getChartSymbol(); BufferedImage overlayLegend = lgds.getOverlayLegend(); if (sld.isChoroplethsEnabled()) { graphics.setFont(FONT_BOLD); graphics.drawString("Choroplets: ", cur_h, cur_v + 15); graphics.drawString(lgds.getChoropletIndicator(), cur_h, cur_v + 27); graphics.setFont(FONT_NORMAL); cur_v += 37; for (Representation r : representations) { graphics.setPaint(r.getFill()); graphics.fill(new RoundRectangle2D.Double(cur_h, cur_v, box_w, box_h, 5, 5)); graphics.setColor(r.getOutline()); graphics.draw(new RoundRectangle2D.Double(cur_h, cur_v, box_w, box_h, 5, 5)); String text; if ("UniqueInterval".equals(sld.getClassificationMethod())) text = String.format("%.1f", r.getRange().getMinValue()); else text = String.format("%.1f %s %.1f", r.getRange().getMinValue(), " to ", r.getRange().getMaxValue()); graphics.setColor(Color.BLACK); graphics.drawString(text, cur_h + box_w + 5, cur_v + box_h - 6); cur_v += (box_h + 5); } } if (sld.isOverlaySymbolsEnabled()) { if (sld.getSymbolType() == SYMBOL_TYPE.PROPORTIONAL_SYMBOLS) { graphics.setFont(FONT_BOLD); graphics.drawString("Proportional symbols: ", cur_h, cur_v + box_h + 6); graphics.drawString(lgds.getSymbolIndicator(), cur_h, cur_v + box_h + 18); graphics.setFont(FONT_NORMAL); cur_v += 48; Color fillColor = new Color(0x5858d5); Color drawColor = new Color(0xccc); int minSize = sld.getMinOverlaySize(); int maxSize = sld.getMaxOverlaySize(); double minValue, maxValue; MaxVisitor maxVisitor = new MaxVisitor(sld.getOverlayIndicators()[0]); results.getFeatures().accepts(maxVisitor, new NullProgressListener()); MinVisitor minVisitor = new MinVisitor(sld.getOverlayIndicators()[0]); results.getFeatures().accepts(minVisitor, new NullProgressListener()); try { maxValue = ((Number)maxVisitor.getMax()).doubleValue(); minValue = ((Number)minVisitor.getMin()).doubleValue(); } catch (IllegalStateException e) { return; } double incSize = (maxSize - minSize) / (N_PROP_CLASSES - 1); double incValue = (maxValue - minValue) / (N_PROP_CLASSES - 1); for (int i = 0; i < N_PROP_CLASSES; i++) { int size = (int) (maxSize - incSize * i); double value = maxValue - incValue * i; int offset = (maxSize - size) / 2; graphics.setColor(fillColor); graphics.fillArc(cur_h + offset, cur_v + i * 25 + offset, size, size, 0, 360); graphics.setColor(drawColor); graphics.drawArc(cur_h + offset, cur_v + i * 25 + offset, size, size, 0, 360); graphics.setColor(Color.BLACK); graphics.drawString(String.format("%.1f", value), cur_h + maxSize + 6, cur_v + i * 25 + maxSize / 2 + 4); } } else if ((sld.getSymbolType() == SYMBOL_TYPE.PIES) || (sld.getSymbolType() == SYMBOL_TYPE.BARS)) { graphics.setFont(FONT_BOLD); cur_v += (box_h + 6); graphics.drawString("Symbols legend: ", cur_h, cur_v); graphics.drawString(lgds.getSymbolIndicator(), cur_h, cur_v + 12); graphics.setFont(FONT_NORMAL); cur_v += 24; if (chartSymbol != null) { graphics.drawImage(chartSymbol, cur_h, cur_v, chartSymbol.getWidth(), chartSymbol.getHeight(), null); cur_v += (chartSymbol.getHeight() + 12); } if (overlayLegend != null) { graphics.drawImage(overlayLegend, cur_h, cur_v, overlayLegend.getWidth(), overlayLegend.getHeight(), null); } } } } }