Package org.mapfish.print.processor.map.scalebar

Source Code of org.mapfish.print.processor.map.scalebar.ScalebarGraphic

/*
* Copyright (C) 2014  Camptocamp
*
* This file is part of MapFish Print
*
* MapFish Print is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MapFish Print is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MapFish Print.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.mapfish.print.processor.map.scalebar;

import com.google.common.annotations.VisibleForTesting;
import org.apache.batik.svggen.SVGGraphics2D;
import org.geotools.referencing.GeodeticCalculator;
import org.mapfish.print.attribute.ScalebarAttribute.ScalebarAttributeValues;
import org.mapfish.print.attribute.map.MapAttribute.MapAttributeValues;
import org.mapfish.print.attribute.map.MapBounds;
import org.mapfish.print.config.Template;
import org.mapfish.print.map.DistanceUnit;
import org.mapfish.print.map.Scale;
import org.mapfish.print.processor.map.CreateMapProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.xml.parsers.ParserConfigurationException;

/**
* Creates a scalebar graphic.
*/
public class ScalebarGraphic {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScalebarGraphic.class);

    private static final int MAX_NUMBER_LAYOUTING_TRIES = 3;

    /**
     * Render the scalebar.
     *  @param mapParams        The parameters of the map for which the scalebar is created.
     * @param scalebarParams    The scalebar parameters.
     * @param tempFolder        The directory in which the graphic file is created.
     * @param template          The template that containts the scalebar processor
     */
    public final URI render(final MapAttributeValues mapParams,
                            final ScalebarAttributeValues scalebarParams,
                            final File tempFolder,
                            final Template template)
            throws IOException, ParserConfigurationException {
        final double dpi = mapParams.getDpi();
        final double dpiRatio = dpi / mapParams.getRequestorDPI();

        // get the map bounds
        final Rectangle paintArea = new Rectangle(mapParams.getMapSize());
        MapBounds bounds = mapParams.getMapBounds();
        bounds = CreateMapProcessor.adjustBoundsToScaleAndMapSize(
                mapParams, mapParams.getRequestorDPI(), paintArea, bounds);
        paintArea.setBounds(0, 0, (int) (paintArea.width * dpiRatio), (int) (paintArea.height * dpiRatio));

        final DistanceUnit mapUnit = getUnit(bounds);
        // to calculate the scale the requestor DPI is used , because the paint area is already adjusted
        final Scale scale = bounds.getScaleDenominator(paintArea, mapParams.getRequestorDPI());

        DistanceUnit scaleUnit = scalebarParams.getUnit();
        if (scaleUnit == null) {
            scaleUnit = mapUnit;
        }

        // adjust scalebar width and height to the DPI value
        final int maxWidthInPixelAdjusted = (int) (scalebarParams.getSize().width * dpiRatio);
        final int maxHeightInPixelAdjusted = (int) (scalebarParams.getSize().height * dpiRatio);
        final int maxLengthInPixelAdjusted = (scalebarParams.getOrientation().isHorizontal()) ?
                maxWidthInPixelAdjusted : maxHeightInPixelAdjusted;

        final double maxIntervalLengthInWorldUnits = DistanceUnit.PX.convertTo(maxLengthInPixelAdjusted, scaleUnit)
                * scale.getDenominator() / scalebarParams.intervals;
        final double niceIntervalLengthInWorldUnits =
                getNearestNiceValue(maxIntervalLengthInWorldUnits, scaleUnit, scalebarParams.lockUnits);

        final ScaleBarRenderSettings settings = new ScaleBarRenderSettings();
        settings.setParams(scalebarParams);
        settings.setMaxSize(new Dimension(maxWidthInPixelAdjusted, maxHeightInPixelAdjusted));
        settings.setDpiRatio(dpiRatio);
        settings.setPadding(getPadding(settings));

        // start the rendering
        File path = null;
        if (template.getConfiguration().renderAsSvg(scalebarParams.renderAsSvg)) {
            // render scalebar as SVG
            final SVGGraphics2D graphics2D = CreateMapProcessor.getSvgGraphics(
                    new Dimension(maxWidthInPixelAdjusted, maxHeightInPixelAdjusted));

            try {
                tryLayout(graphics2D, scaleUnit, scale, niceIntervalLengthInWorldUnits, settings, 0);

                path = File.createTempFile("scalebar-graphic-", ".svg", tempFolder);
                CreateMapProcessor.saveSvgFile(graphics2D, path);
            } finally {
                graphics2D.dispose();
            }
        } else {
            // render scalebar as raster graphic
            final BufferedImage bufferedImage = new BufferedImage(maxWidthInPixelAdjusted, maxHeightInPixelAdjusted,
                    BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics2D graphics2D = bufferedImage.createGraphics();

            try {
                tryLayout(graphics2D, scaleUnit, scale, niceIntervalLengthInWorldUnits, settings, 0);

                path = File.createTempFile("scalebar-graphic-", ".tiff", tempFolder);
                ImageIO.write(bufferedImage, "tiff", path);
            } finally {
                graphics2D.dispose();
            }
        }

        return path.toURI();
    }

    private DistanceUnit getUnit(final MapBounds bounds) {
        GeodeticCalculator calculator = new GeodeticCalculator(bounds.getProjection());
        return DistanceUnit.fromString(calculator.getEllipsoid().getAxisUnit().toString());
    }

    /**
     * Try recursively to find the correct layout.
     */
    private void tryLayout(final Graphics2D graphics2D, final DistanceUnit scaleUnit, final Scale scale,
            final double intervalLengthInWorldUnits, final ScaleBarRenderSettings settings, final int tryNumber) {
        if (tryNumber > MAX_NUMBER_LAYOUTING_TRIES) {
            // if no good layout can be found, stop. an empty scalebar graphic will be shown.
            LOGGER.error("layouting the scalebar failed (unit: " + scaleUnit.toString()
                    + ", scale: " + scale.getDenominator() + ")");
            return;
        }

        final ScalebarAttributeValues scalebarParams = settings.getParams();
        final DistanceUnit intervalUnit = bestUnit(scaleUnit, intervalLengthInWorldUnits, scalebarParams.lockUnits);
        final float intervalLengthInPixels = (float) scaleUnit.convertTo(
                intervalLengthInWorldUnits / scale.getDenominator(), DistanceUnit.PX);

        //compute the label positions
        final List<Label> labels = new ArrayList<Label>(scalebarParams.intervals + 1);
        final float leftLabelMargin;
        final float rightLabelMargin;
        final float topLabelMargin;
        final float bottomLabelMargin;

        final Font font = new Font(scalebarParams.font, Font.PLAIN, getFontSize(settings));
        final FontRenderContext frc = new FontRenderContext(null, true, true);

        if (scalebarParams.intervals > 1 || scalebarParams.subIntervals) {
            //the label will be centered under each tick mark
            for (int i = 0; i <= scalebarParams.intervals; i++) {
                String labelText = createLabelText(scaleUnit, intervalLengthInWorldUnits * i, intervalUnit);
                if (i == scalebarParams.intervals) {
                    // only show unit for the last label
                    labelText += intervalUnit;
                }
                TextLayout labelLayout = new TextLayout(labelText, font, frc);
                labels.add(new Label(intervalLengthInPixels * i, labelLayout, scalebarParams.getOrientation()));
            }
            leftLabelMargin = labels.get(0).getWidth() / 2.0f;
            rightLabelMargin = labels.get(labels.size() - 1).getWidth() / 2.0f;
            topLabelMargin = labels.get(0).getHeight() / 2.0f;
            bottomLabelMargin = labels.get(labels.size() - 1).getHeight() / 2.0f;
        } else {
            //if there is only one interval, place the label centered between the two tick marks
            String labelText = createLabelText(scaleUnit, intervalLengthInWorldUnits, intervalUnit) + intervalUnit;
            TextLayout labelLayout = new TextLayout(labelText, font, frc);
            final Label label = new Label(intervalLengthInPixels / 2.0f,
                    labelLayout, scalebarParams.getOrientation());
            labels.add(label);
            rightLabelMargin = Math.max(0.0f, label.getWidth() - intervalLengthInPixels) / 2.0f;
            leftLabelMargin = rightLabelMargin;
            topLabelMargin = Math.max(0.0f, label.getHeight() - intervalLengthInPixels) / 2.0f;
            bottomLabelMargin = topLabelMargin;
        }

        if (fitsAvailableSpace(scalebarParams, intervalLengthInPixels,
                leftLabelMargin, rightLabelMargin, topLabelMargin, bottomLabelMargin, settings)) {
            //the layout fits the maxSize
            settings.setLabels(labels);
            settings.setScaleUnit(scaleUnit);
            settings.setIntervalLengthInPixels(intervalLengthInPixels);
            settings.setIntervalLengthInWorldUnits(intervalLengthInWorldUnits);
            settings.setIntervalUnit(intervalUnit);
            settings.setLeftLabelMargin(leftLabelMargin);
            settings.setRightLabelMargin(rightLabelMargin);
            settings.setTopLabelMargin(topLabelMargin);
            settings.setBottomLabelMargin(bottomLabelMargin);

            doLayout(graphics2D, scalebarParams, settings);
        } else {
            //not enough room because of the labels, try a smaller bar
            //CSOFF: MagicNumber
            double nextIntervalDistance = getNearestNiceValue(intervalLengthInWorldUnits * 0.9, scaleUnit, scalebarParams.lockUnits);
            //CSOFF: MagicNumber
            tryLayout(graphics2D, scaleUnit, scale, nextIntervalDistance, settings, tryNumber + 1);
        }
    }

    private boolean fitsAvailableSpace(final ScalebarAttributeValues scalebarParams,
            final float intervalWidthInPixels, final float leftLabelMargin,
            final float rightLabelMargin, final float topLabelMargin,
            final float bottomLabelMargin, final ScaleBarRenderSettings settings) {
        if (scalebarParams.getOrientation().isHorizontal()) {
            return scalebarParams.intervals * intervalWidthInPixels + leftLabelMargin + rightLabelMargin + 2 * settings.getPadding()
                    <= settings.getMaxSize().width;
        } else {
            return scalebarParams.intervals * intervalWidthInPixels + topLabelMargin + bottomLabelMargin + 2 * settings.getPadding()
                    <= settings.getMaxSize().height;
        }
    }

    /**
     * Called when the position of the labels and their content is known.
     * <p/>
     * Creates the drawer which draws the scalebar.
     */
    private void doLayout(final Graphics2D graphics2d, final ScalebarAttributeValues scalebarParams,
            final ScaleBarRenderSettings settings) {
        final Dimension maxLabelSize = getMaxLabelSize(settings);

        int numSubIntervals = 1;
        if (scalebarParams.subIntervals) {
            numSubIntervals = getNbSubIntervals(
                    settings.getScaleUnit(), settings.getIntervalLengthInWorldUnits(), settings.getIntervalUnit());
        }

        settings.setBarSize(getBarSize(settings));
        settings.setLabelDistance(getLabelDistance(settings));
        settings.setLineWidth(getLineWidth(settings));
        settings.setSize(getSize(scalebarParams, settings, maxLabelSize));
        settings.setMaxLabelSize(maxLabelSize);
        settings.setNumSubIntervals(numSubIntervals);

        ScalebarDrawer drawer = scalebarParams.getType().createDrawer(graphics2d, settings);
        drawer.draw();
    }

    /**
     * Get the size of the painting area required to draw the scalebar with labels.
     *
     * @param scalebarParams    Parameters for the scalebar.
     * @param settings          Parameters for rendering the scalebar.
     * @param maxLabelSize      The max. size of the labels.
     */
    @VisibleForTesting
    protected static Dimension getSize(final ScalebarAttributeValues scalebarParams,
            final ScaleBarRenderSettings settings, final Dimension maxLabelSize) {
        final float width;
        final float height;
        if (scalebarParams.getOrientation().isHorizontal()) {
            width = 2 * settings.getPadding()
                + settings.getIntervalLengthInPixels() * scalebarParams.intervals
                + settings.getLeftLabelMargin() + settings.getRightLabelMargin();
            height = 2 * settings.getPadding()
                + settings.getBarSize() + settings.getLabelDistance()
                + maxLabelSize.height;
        } else {
            width = 2 * settings.getPadding()
                + maxLabelSize.width + settings.getLabelDistance()
                + settings.getBarSize();
            height = 2 * settings.getPadding()
                + settings.getTopLabelMargin()
                + settings.getIntervalLengthInPixels() * scalebarParams.intervals
                + settings.getBottomLabelMargin();
        }
        return new Dimension((int) Math.ceil(width), (int) Math.ceil(height));
    }

    /**
     * Get the maximum width and height of the labels.
     * @param settings Parameters for rendering the scalebar.
     */
    @VisibleForTesting
    protected static Dimension getMaxLabelSize(final ScaleBarRenderSettings settings) {
        float maxLabelHeight = 0.0f;
        float maxLabelWidth = 0.0f;
        for (int i = 0; i < settings.getLabels().size(); i++) {
            Label label = settings.getLabels().get(i);
            maxLabelHeight = Math.max(maxLabelHeight, label.getHeight());
            maxLabelWidth = Math.max(maxLabelWidth, label.getWidth());
        }
        return new Dimension((int) Math.ceil(maxLabelWidth), (int) Math.ceil(maxLabelHeight));
    }

    /**
     * Format the label text.
     * @param scaleUnit     The unit used for the scalebar.
     * @param value         The scale value.
     * @param intervalUnit  The scaled unit for the intervals.
     */
    @VisibleForTesting
    protected static String createLabelText(final DistanceUnit scaleUnit, final double value, final DistanceUnit intervalUnit) {
        double scaledValue = scaleUnit.convertTo(value, intervalUnit);

        //CSOFF: MagicNumber
        // assume that there is no interval smaller then 0.0001
        scaledValue = Math.round(scaledValue * 10000) / 10000;
        //CSON: MagicNumber
        String decimals = Double.toString(scaledValue).split("\\.")[1];

        if (Double.valueOf(decimals) == 0) {
            return Long.toString(Math.round(scaledValue));
        } else {
            return Double.toString(scaledValue);
        }
    }

    private DistanceUnit bestUnit(final DistanceUnit scaleUnit, final double intervalDistance, final boolean lockUnits) {
        if (lockUnits) {
            return scaleUnit;
        } else {
            return DistanceUnit.getBestUnit(intervalDistance, scaleUnit);
        }
    }

    /**
     * Reduce the given value to the nearest smaller 1 significant digit number starting
     * with 1, 2 or 5.
     * @param value     the value to find a nice number for.
     * @param scaleUnit the unit of the value.
     * @param lockUnits if set, the values are not scaled to a "nicer" unit.
     */
    @VisibleForTesting
    //CSOFF: MagicNumber
    protected final double getNearestNiceValue(final double value, final DistanceUnit scaleUnit, final boolean lockUnits) {
        DistanceUnit bestUnit = bestUnit(scaleUnit, value, lockUnits);
        double factor = scaleUnit.convertTo(1.0, bestUnit);

        // nearest power of 10 lower than value
        int digits = (int) Math.floor((Math.log(value * factor) / Math.log(10)));
        double pow10 = Math.pow(10, digits);

        // ok, find first character
        double firstChar = value * factor / pow10;

        // right, put it into the correct bracket
        int barLen;
        if (firstChar >= 10.0) {
            barLen = 10;
        } else if (firstChar >= 5.0) {
            barLen = 5;
        } else if (firstChar >= 2.0) {
            barLen = 2;
        } else {
            barLen = 1;
        }

        // scale it up the correct power of 10
        return barLen * pow10 / factor;
    }
    //CSON: MagicNumber

    /**
     * @return The "nicest" number of sub intervals in function of the interval distance.
     */
    //CSOFF: MagicNumber
    private int getNbSubIntervals(final DistanceUnit scaleUnit, final double intervalDistance, final DistanceUnit intervalUnit) {
        double value = scaleUnit.convertTo(intervalDistance, intervalUnit);
        int digits = (int) (Math.log(value) / Math.log(10));
        double pow10 = Math.pow(10, digits);

        // ok, find first character
        int firstChar = (int) (value / pow10);
        switch (firstChar) {
            case 1:
                return 2;
            case 2:
                return 2;
            case 5:
                return 5;
            case 10:
                return 2;
            default:
                throw new RuntimeException("Invalid interval: " + value + intervalUnit + " (" + firstChar + ")");
        }
    }
    //CSON: MagicNumber

    private int getFontSize(final ScaleBarRenderSettings settings) {
        return (int) Math.ceil(settings.getParams().fontSize * settings.getDpiRatio());
    }

    //CSOFF: MagicNumber
    private static int getLineWidth(final ScaleBarRenderSettings settings) {
        if (settings.getParams().lineWidth != null) {
            return (int) Math.ceil(settings.getParams().lineWidth * settings.getDpiRatio());
        } else {
            if (settings.getParams().getOrientation().isHorizontal()) {
                return settings.getMaxSize().width / 150;
            } else {
                return settings.getMaxSize().height / 150;
            }
        }
    }
    //CSON: MagicNumber

    /**
     * Get the bar size.
     * @param settings Parameters for rendering the scalebar.
     */
    //CSOFF: MagicNumber
    @VisibleForTesting
    protected static int getBarSize(final ScaleBarRenderSettings settings) {
        if (settings.getParams().barSize != null) {
            return (int) Math.ceil(settings.getParams().barSize * settings.getDpiRatio());
        } else {
            if (settings.getParams().getOrientation().isHorizontal()) {
                return settings.getMaxSize().height / 4;
            } else {
                return settings.getMaxSize().width / 4;
            }
        }
    }
    //CSON: MagicNumber

    /**
     * Get the label distance..
     * @param settings Parameters for rendering the scalebar.
     */
    //CSOFF: MagicNumber
    @VisibleForTesting
    protected static int getLabelDistance(final ScaleBarRenderSettings settings) {
        if (settings.getParams().labelDistance != null) {
            return (int) Math.ceil(settings.getParams().labelDistance * settings.getDpiRatio());
        } else {
            if (settings.getParams().getOrientation().isHorizontal()) {
                return settings.getMaxSize().width / 40;
            } else {
                return settings.getMaxSize().height / 40;
            }
        }
    }
    //CSON: MagicNumber

    //CSOFF: MagicNumber
    private static int getPadding(final ScaleBarRenderSettings settings) {
        if (settings.getParams().padding != null) {
            return (int) Math.ceil(settings.getParams().padding * settings.getDpiRatio());
        } else {
            if (settings.getParams().getOrientation().isHorizontal()) {
                return settings.getMaxSize().width / 40;
            } else {
                return settings.getMaxSize().height / 40;
            }
        }
    }
    //CSON: MagicNumber
}
TOP

Related Classes of org.mapfish.print.processor.map.scalebar.ScalebarGraphic

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.