org.geomajas.plugin.print.component.impl.ScaleBarComponentImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.geomajas.plugin.print.component.impl.ScaleBarComponentImpl.java

Source

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2014 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.plugin.print.component.impl;

import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;

import org.geomajas.configuration.client.ClientMapInfo;
import org.geomajas.configuration.client.UnitType;
import org.geomajas.geometry.Crs;
import org.geomajas.plugin.print.component.LayoutConstraint;
import org.geomajas.plugin.print.component.PdfContext;
import org.geomajas.plugin.print.component.PrintComponentVisitor;
import org.geomajas.plugin.print.component.ScaleBarComponent;
import org.geomajas.plugin.print.component.dto.ScaleBarComponentInfo;
import org.geomajas.plugin.print.component.service.PrintConfigurationService;
import org.geomajas.plugin.print.component.service.PrintDtoConverterService;
import org.geomajas.plugin.print.parser.FontConverter;
import org.geomajas.service.GeoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.lowagie.text.Rectangle;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

/**
 * Scale bar component for inclusion in printed documents.
 * 
 * @author Jan De Moerloose
 */
@Component()
@Scope(value = "prototype")
public class ScaleBarComponentImpl extends AbstractPrintComponent<ScaleBarComponentInfo>
        implements ScaleBarComponent {

    @XStreamOmitField
    private final Logger log = LoggerFactory.getLogger(ScaleBarComponentImpl.class);

    /** The unit (meter, mile, degree). */
    private String unit = "units";

    /** The number of tics for the scale bar. */
    private int ticNumber;

    /** The labels on top of the ticks. */
    private List<String> tickLabels = new ArrayList<String>();

    /** The calculated sizes of the ticks. */
    private List<Rectangle> tickSizes = new ArrayList<Rectangle>();

    /** The label font. */
    @XStreamConverter(FontConverter.class)
    private Font font = new Font("Dialog", Font.PLAIN, 12);

    /** Width a tic (double checkerboard style). */
    private float ticWidth;

    /** Height of a tic (double checkerboard style). */
    private float ticHeight;

    @Autowired
    @XStreamOmitField
    private PrintConfigurationService configurationService;

    @Autowired
    @XStreamOmitField
    private GeoService geoService;

    @Autowired
    @XStreamOmitField
    private PrintDtoConverterService converterService;

    private static final float[] ALLOW_TICK_NUMBERS = new float[] { 1, 2, 5, 10, 25, 50, 100, 200, 250, 500, 750 };

    private static final String[] UNIT_PREFIXES = new String[] { "n", "m", "", "k", "M" };

    private static final float FEET_PER_METER = 3.2808399f;

    private static final float FEET_PER_MILE = 5280f;

    /** Default constructor. */
    public ScaleBarComponentImpl() {
        getConstraint().setAlignmentX(LayoutConstraint.LEFT);
        getConstraint().setAlignmentY(LayoutConstraint.BOTTOM);
        getConstraint().setMarginX(20);
        getConstraint().setMarginY(20);
        getConstraint().setWidth(200);
    }

    /**
     * Get font.
     * 
     * @return font
     */
    public Font getFont() {
        return font;
    }

    /**
     * Set font.
     * 
     * @param font
     *            font
     */
    public void setFont(Font font) {
        this.font = font;
    }

    /**
     * Call back visitor.
     * 
     * @param visitor
     *            visitor
     */
    public void accept(PrintComponentVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public void calculateSize(PdfContext context) {
        // clear labels and sizes to make this method idempotent
        tickLabels.clear();
        tickSizes.clear();

        // the width must be set !!
        float width = getConstraint().getWidth();

        ClientMapInfo map = configurationService.getMapInfo(getMap().getMapId(), getMap().getApplicationId());
        boolean isEnglishUnits = false;
        boolean isEnglishMiles = false;
        float pxPUnit = getMap().getPpUnit();
        String englishUnit = "ft";
        // Calculate the labels
        if (map != null) {
            UnitType displayUnitType = map.getDisplayUnitType();
            log.debug("calculateSize getMap.getId({}), res {}", getMap().getId(), map);
            Crs crs;
            try {
                crs = geoService.getCrs2(map.getCrs());
                unit = crs.getCoordinateSystem().getAxis(0).getUnit().toString();
                if (UnitType.ENGLISH == displayUnitType || UnitType.ENGLISH_FOOT == displayUnitType) {
                    isEnglishUnits = true;
                    if (!unit.toUpperCase().contains("F")) {
                        pxPUnit = pxPUnit / FEET_PER_METER;
                    }

                    // check for yards conversion
                    if (UnitType.ENGLISH == displayUnitType && (width / pxPUnit) >= 3
                            && (width / pxPUnit) < FEET_PER_MILE) {
                        pxPUnit = pxPUnit * 3f;
                        englishUnit = "yd";
                    }

                    // check for miles conversion
                    if ((width / pxPUnit) > FEET_PER_MILE) {
                        pxPUnit = pxPUnit * FEET_PER_MILE;
                        englishUnit = "mi";
                        isEnglishMiles = true;
                    }
                }
            } catch (Exception e) { // NOSONAR
                throw new IllegalStateException("Could not calculate map unit", e);
            }
        }
        // Calculate width in map units and round.
        float widthInUnits = width / pxPUnit;

        // Calculate ideal tic width in units
        float ticWidthInUnits = widthInUnits / getTicNumber();

        // Calculate minimal tic width in units
        int ticLog;
        float minTicWidthInUnits;
        if (!isEnglishMiles) {
            ticLog = (int) Math.floor(Math.log10(ticWidthInUnits) / 3);
            minTicWidthInUnits = (float) Math.pow(10, ticLog * 3);
        } else {
            ticLog = (int) Math.floor(Math.log10(ticWidthInUnits));
            minTicWidthInUnits = (float) Math.pow(10, ticLog);
        }

        // Find the highest nice number
        float nicestNumber = 0;
        for (float allowTickNumber : ALLOW_TICK_NUMBERS) {
            if (minTicWidthInUnits * allowTickNumber > ticWidthInUnits) {
                break;
            } else {
                nicestNumber = allowTickNumber;
            }
        }
        ticWidthInUnits = minTicWidthInUnits * nicestNumber;
        ticWidth = ticWidthInUnits * pxPUnit;
        ticHeight = ticWidth * 0.3f;
        int ticCount = getTicNumber();

        // font = new Font("Dialog", Font.PLAIN, (int) (0.8 * ticHeight));
        // set the Unit Prefixes

        String units;
        if (!isEnglishUnits && !isEnglishMiles) {
            units = ((ticLog >= -2 && ticLog <= 2) ? UNIT_PREFIXES[ticLog + 2] : "*10^" + (ticLog * 3)) + unit;
        } else {
            units = englishUnit;
        }

        // First pass to check if all fractions can be ignored
        boolean ignoreAllFractions = true;
        for (int i = 0; i <= ticCount; i++) {
            double valueLabel;
            if (!isEnglishUnits && !isEnglishMiles) {
                valueLabel = i * ticWidthInUnits / Math.pow(10, 3 * ticLog);

            } else {
                valueLabel = i * ticWidthInUnits;
            }
            double rounded = Math.round(valueLabel);
            if (Math.abs(valueLabel - (double) rounded) > 0.001) {
                ignoreAllFractions = false;
                break; // Abort
            }
        }

        for (int i = 0; i <= ticCount; i++) {
            String label;

            if (!isEnglishUnits && !isEnglishMiles) {
                label = scaleLabelOnPrint(i * ticWidthInUnits / Math.pow(10, 3 * ticLog), ignoreAllFractions);

            } else {
                label = scaleLabelOnPrint(i * ticWidthInUnits, ignoreAllFractions);

            }

            if (i == ticCount) {
                label += " " + units;
            }
            tickLabels.add(label);
            tickSizes.add(context.getTextSize(label, font));
        }

        // Calculate size
        width = ticWidth * ticCount;
        Rectangle first = context.getTextSize(tickLabels.get(0), font);
        Rectangle last = context.getTextSize(tickLabels.get(ticCount), font);
        width += 0.5 * first.getWidth();
        width += 0.5 * last.getWidth();
        float height = ticHeight;
        height += first.getHeight();
        setBounds(new Rectangle(0, 0, width, height));
    }

    private String scaleLabelOnPrint(double value, boolean ignoreAllFractions) {

        String valueAsString;
        if (ignoreAllFractions) {
            valueAsString = Long.toString(Math.round(value));
        } else {
            valueAsString = Double.toString(value);
        }
        return valueAsString;
    }

    @Override
    public void render(PdfContext context) {
        // draw the tics
        float lowX = 0.5f * tickSizes.get(0).getWidth();
        float lowY = 0;
        float highX = lowX;
        float highY = 0.333f * ticHeight;
        Rectangle baseRect = new Rectangle(0, 0, ticWidth, 0.333f * ticHeight);

        // fills
        for (int i = 0; i < ticNumber; i++) {
            if (i % 2 == 0) {
                context.moveRectangleTo(baseRect, lowX, lowY);
                context.fillRectangle(baseRect, Color.white);
                context.strokeRectangle(baseRect, Color.black, 0.5f);
                context.moveRectangleTo(baseRect, highX, highY);
                context.fillRectangle(baseRect, Color.black);
                context.strokeRectangle(baseRect, Color.black, 0.5f);
            } else {
                context.moveRectangleTo(baseRect, highX, highY);
                context.fillRectangle(baseRect, Color.white);
                context.strokeRectangle(baseRect, Color.black, 0.5f);
                context.moveRectangleTo(baseRect, lowX, lowY);
                context.fillRectangle(baseRect, Color.black);
                context.strokeRectangle(baseRect, Color.black, 0.5f);
            }
            lowX += ticWidth;
            highX += ticWidth;
        }

        // tick extensions
        highX = 0.5f * tickSizes.get(0).getWidth();
        highY = 0.6666f * ticHeight;
        for (int i = 0; i <= ticNumber; i++) {
            context.drawRelativePath(new float[] { 0, 0 }, new float[] { 0, 1 },
                    new Rectangle(highX, highY, highX, 0.75f * ticHeight), Color.black, 0.5f, null);
            highX += ticWidth;
        }

        // position and print the labels
        float labelX = 0.5f * tickSizes.get(0).getWidth();
        float labelY = ticHeight;
        for (int i = 0; i < tickLabels.size(); i++) {
            Rectangle box = tickSizes.get(i);
            // center the label
            context.moveRectangleTo(box, labelX - 0.5f * box.getWidth(), labelY);
            context.drawText(tickLabels.get(i), font, box, Color.black);
            labelX += ticWidth;
        }
    }

    MapComponentImpl getMap() {
        return (MapComponentImpl) getParent();
    }

    @Override
    public int getTicNumber() {
        return ticNumber;
    }

    public void setTicNumber(int ticNumber) {
        this.ticNumber = ticNumber;
    }

    @Override
    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    @Override
    public void fromDto(ScaleBarComponentInfo scaleBarInfo) {
        super.fromDto(scaleBarInfo);
        setTicNumber(scaleBarInfo.getTicNumber());
        setUnit(scaleBarInfo.getUnit());
        setFont(converterService.toInternal(scaleBarInfo.getFont()));
    }
}