/* (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.decoration; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.wms.WMSMapContent; import static org.geoserver.wms.decoration.ScaleLineDecoration.MeasurementSystem.*; public class ScaleLineDecoration implements MapDecoration { /** A logger for this class. */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wms.responses"); private static Map<String, Double>INCHES_PER_UNIT = new HashMap<String, Double> (); static { INCHES_PER_UNIT.put("inches", 1.0); INCHES_PER_UNIT.put("ft", 12.0); INCHES_PER_UNIT.put("mi", 63360.0); INCHES_PER_UNIT.put("m", 39.3701); INCHES_PER_UNIT.put("km", 39370.1); INCHES_PER_UNIT.put("dd", 4374754.0); INCHES_PER_UNIT.put("yd", 36.0); } public static final String topOutUnits = "km"; public static final String topInUnits = "m"; public static final String bottomOutUnits = "mi"; public static final String bottomInUnits = "ft"; public static final int suggestedWidth = 100; private float fontSize = 10; private float dpi = 25.4f / 0.28f; /// OGC Spec for SLD private float strokeWidth = 2; private Color bgcolor = Color.WHITE; private Color fgcolor = Color.BLACK; private Boolean transparent = Boolean.FALSE; private MeasurementSystem measurementSystem = BOTH; static enum MeasurementSystem { METRIC, IMPERIAL, BOTH; static MeasurementSystem mapToEnum(String type) throws Exception { switch(type){ case "metric": return METRIC; case "imperial": return IMPERIAL; case "both": return BOTH; default: throw new Exception("Wrong input parameter"); } } } public void loadOptions(Map<String, String> options) { if (options.get("fontsize") != null) { try { this.fontSize = Float.parseFloat(options.get("fontsize")); } catch (Exception e) { this.LOGGER.log(Level.WARNING, "'fontsize' must be a float.", e); } } if (options.get("dpi") != null) { try { this.dpi = Float.parseFloat(options.get("dpi")); } catch (Exception e) { this.LOGGER.log(Level.WARNING, "'dpi' must be a float.", e); } } if (options.get("strokewidth") != null) { try { this.strokeWidth = Float.parseFloat(options.get("strokeWidth")); } catch (Exception e) { this.LOGGER.log(Level.WARNING, "'strokewidth' must be a float.", e); } } Color tmp = MapDecorationLayout.parseColor(options.get("bgcolor")); if (tmp != null) bgcolor = tmp; tmp = MapDecorationLayout.parseColor(options.get("fgcolor")); if (tmp != null) fgcolor = tmp; // Creates a rectangle only if is defined, if not is "transparent" like Google Maps if (options.get("transparent") != null) { try { this.transparent = Boolean.parseBoolean(options.get("transparent")); } catch (Exception e) { this.LOGGER.log(Level.WARNING, "'transparent' must be a boolean.", e); } } if(options.get("measurement-system") != null){ try { LOGGER.log(Level.INFO,options.get("measurement-system")); this.measurementSystem = MeasurementSystem.mapToEnum(options.get("measurement-system")); } catch (Exception e) { this.LOGGER.log(Level.WARNING, "'measurement-system' must be one of 'metric', 'imperial' or 'both'.", e); } } } public Dimension findOptimalSize(Graphics2D g2d, WMSMapContent mapContent){ FontMetrics metrics = g2d.getFontMetrics(g2d.getFont()); return new Dimension( suggestedWidth, 8 + (metrics.getHeight() + metrics.getDescent()) * 2 ); } public int getBarLength(double maxLength) { int digits = (int)(Math.log(maxLength) / Math.log(10)); double pow10 = Math.pow(10, digits); // Find first character int firstCharacter = (int)(maxLength / pow10); int barLength; if (firstCharacter > 5) { barLength = 5; } else if (firstCharacter > 2) { barLength = 2; } else { barLength = 1; } return (int)(barLength * pow10); } public void paint(Graphics2D g2d, Rectangle paintArea, WMSMapContent mapContent) throws Exception { Color oldColor = g2d.getColor(); Stroke oldStroke = g2d.getStroke(); Font oldFont = g2d.getFont(); Object oldAntialias = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); // Set the font size. g2d.setFont(oldFont.deriveFont(this.fontSize)); double scaleDenominator = mapContent.getScaleDenominator(true); String curMapUnits = "m"; double normalizedScale = (scaleDenominator > 1.0) ? (1.0 / scaleDenominator) : scaleDenominator; double resolution = 1 / (normalizedScale * INCHES_PER_UNIT.get(curMapUnits) * this.dpi); int maxWidth = suggestedWidth; if (maxWidth > paintArea.getWidth()) { maxWidth = (int)paintArea.getWidth(); } maxWidth = maxWidth - 6; double maxSizeData = maxWidth * resolution * INCHES_PER_UNIT.get(curMapUnits); String topUnits; String bottomUnits; if (maxSizeData > 100000) { topUnits = topOutUnits; bottomUnits = bottomOutUnits; } else { topUnits = topInUnits; bottomUnits = bottomInUnits; } double topMax = maxSizeData / INCHES_PER_UNIT.get(topUnits); double bottomMax = maxSizeData / INCHES_PER_UNIT.get(bottomUnits); int topRounded = this.getBarLength(topMax); int bottomRounded = this.getBarLength(bottomMax); topMax = topRounded / INCHES_PER_UNIT.get(curMapUnits) * INCHES_PER_UNIT.get(topUnits); bottomMax = bottomRounded / INCHES_PER_UNIT.get(curMapUnits) * INCHES_PER_UNIT.get(bottomUnits); int topPx = (int)(topMax / resolution); int bottomPx = (int)(bottomMax / resolution); int centerY = (int)paintArea.getCenterY(); int leftX = (int)paintArea.getMinX() + ((int)paintArea.getWidth() - Math.max(topPx, bottomPx)) / 2; FontMetrics metrics = g2d.getFontMetrics(g2d.getFont()); int prongHeight = metrics.getHeight() + metrics.getDescent(); //Do not antialias scaleline lines g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); // Creates a rectangle only if is defined, if not is "transparent" like Google Maps if (!this.transparent) { Rectangle frame = new Rectangle( leftX - 4, centerY - prongHeight - 4, Math.max(topPx, bottomPx) + 8, 8 + prongHeight * 2 ); // fill the rectangle g2d.setColor(bgcolor); g2d.fill(frame); // draw the border frame.height -= 1; frame.width -= 1; g2d.setColor(fgcolor); g2d.setStroke(new BasicStroke(1)); g2d.draw(frame); } else { g2d.setColor(fgcolor); } g2d.setStroke(new BasicStroke(2)); if(measurementSystem == METRIC || measurementSystem == BOTH) { // Left vertical top bar g2d.drawLine(leftX, centerY, leftX, centerY - prongHeight); // Right vertical top bar g2d.drawLine(leftX + topPx, centerY, leftX + topPx, centerY - prongHeight); // Draw horizontal line for metric g2d.drawLine(leftX, centerY, leftX + topPx, centerY); //Antialias text if enabled g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntialias); // Draw text metric String topText = topRounded + " " + topUnits; g2d.drawString(topText, leftX + (int)((topPx - metrics.stringWidth(topText)) / 2), centerY - prongHeight + metrics.getAscent() ); } if(measurementSystem == IMPERIAL || measurementSystem == BOTH){ //Do not antialias scaleline lines g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); //Left vertical bottom bar g2d.drawLine(leftX, centerY + prongHeight, leftX, centerY); // Right vertical bottom bar g2d.drawLine(leftX + bottomPx, centerY, leftX + bottomPx, centerY + prongHeight); // Draw horizontal for imperial g2d.drawLine(leftX, centerY, leftX + bottomPx, centerY); //Antialias text if enabled g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntialias); // Draw text imperial String bottomText = bottomRounded + " " + bottomUnits; g2d.drawString(bottomText, leftX + (int) ((bottomPx - metrics.stringWidth(bottomText)) / 2), centerY + metrics.getHeight() ); } g2d.setColor(oldColor); g2d.setStroke(oldStroke); g2d.setFont(oldFont); } }