All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mapfish.print.attribute.map.MapfishMapContext Maven / Gradle / Ivy

There is a newer version: 3.22.0
Show newest version
package org.mapfish.print.attribute.map;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.mapfish.print.FloatingPointUtil;
import org.mapfish.print.map.Scale;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Utility class that adjusts the bounds and the map size in case a rotation is set. Also it provides an
 * {@link AffineTransform} to render the layer graphics.
 */
public final class MapfishMapContext {

    private final MapBounds bounds;
    private final Dimension mapSize;
    private final double rotation;
    private final double dpi;
    private final boolean forceLongitudeFirst;
    private final boolean dpiSensitiveStyle;
    private final MapfishMapContext parent;

    /**
     * Constructor.
     *
     * @param bounds the map bounds
     * @param mapSize the map size
     * @param dpi the dpi of the printed map
     * @param forceLongitudeFirst If true then force longitude coordinates as the first coordinate.
     * @param dpiSensitiveStyle Scale the vector styles?
     */
    public MapfishMapContext(
            final MapBounds bounds, final Dimension mapSize, final double dpi,
            final Boolean forceLongitudeFirst, final boolean dpiSensitiveStyle) {
        this(null, bounds, mapSize, dpi, forceLongitudeFirst, dpiSensitiveStyle);
    }

    /**
     * Constructor.
     *
     * @param bounds the map bounds
     * @param mapSize the map size
     * @param rotation the rotation
     * @param dpi the dpi of the printed map
     * @param forceLongitudeFirst If true then force longitude coordinates as the first coordinate.
     * @param dpiSensitiveStyle Scale the vector styles?
     */
    public MapfishMapContext(
            final MapBounds bounds, final Dimension mapSize, final double rotation,
            final double dpi, final Boolean forceLongitudeFirst, final boolean dpiSensitiveStyle) {
        this(null, bounds, mapSize, rotation, dpi, forceLongitudeFirst, dpiSensitiveStyle);
    }

    /**
     * Constructor.
     *
     * @param parent the context that this context is derived from
     * @param bounds the map bounds
     * @param mapSize the map size
     * @param dpi the dpi of the printed map
     * @param forceLongitudeFirst If true then force longitude coordinates as the first coordinate.
     * @param dpiSensitiveStyle Scale the vector styles?
     */
    // CSOFF: ParameterNumber
    public MapfishMapContext(
            final MapfishMapContext parent, final MapBounds bounds, final Dimension mapSize,
            final double dpi,
            final Boolean forceLongitudeFirst, final boolean dpiSensitiveStyle) {
        this(parent, bounds, mapSize, 0, dpi, forceLongitudeFirst, dpiSensitiveStyle);
    }

    /**
     * Constructor.
     *
     * @param parent the context that this context is derived from
     * @param bounds the map bounds
     * @param mapSize the map size
     * @param rotation the rotation
     * @param dpi the dpi of the printed map
     * @param forceLongitudeFirst If true then force longitude coordinates as the first coordinate.
     * @param dpiSensitiveStyle Scale the vector styles?
     */
    // CSOFF: ParameterNumber
    public MapfishMapContext(
            final MapfishMapContext parent, final MapBounds bounds, final Dimension mapSize,
            final double rotation, final double dpi, final Boolean forceLongitudeFirst,
            final boolean dpiSensitiveStyle) {
        // CSON: ParameterNumber
        this.parent = parent;
        this.bounds = bounds;
        this.mapSize = mapSize;
        this.rotation = rotation;
        this.dpi = dpi;
        this.forceLongitudeFirst = forceLongitudeFirst == null ? false : forceLongitudeFirst;
        this.dpiSensitiveStyle = dpiSensitiveStyle;
    }

    /**
     * Round the size of a rectangle with double values.
     *
     * @param rectangle The rectangle.
     * @return
     */
    public static Dimension rectangleDoubleToDimension(final Rectangle2D.Double rectangle) {
        return new Dimension(
                (int) Math.round(rectangle.width),
                (int) Math.round(rectangle.height));
    }

    /**
     * @return The rotation in radians.
     */
    public double getRotation() {
        return this.rotation;
    }

    /**
     * @return The rotation in degree.
     */
    public double getRotationDegree() {
        return Math.toDegrees(this.rotation);
    }

    public MapBounds getBounds() {
        return this.bounds;
    }

    /**
     * Return the map bounds rotated with the set rotation.
     *
     * @return Rotated bounds.
     */
    public MapBounds getRotatedBounds() {
        return this.bounds.adjustBoundsToRotation(this.rotation);
    }

    /**
     * Return the map bounds rotated with the set rotation. The bounds are adapted to rounding changes of the
     * size of the paint area.
     *
     * @param paintAreaPrecise The exact size of the paint area.
     * @param paintArea The rounded size of the paint area.
     * @return Rotated bounds.
     */
    public MapBounds getRotatedBounds(final Rectangle2D.Double paintAreaPrecise, final Rectangle paintArea) {
        final MapBounds rotatedBounds = this.getRotatedBounds();

        if (rotatedBounds instanceof CenterScaleMapBounds) {
            return rotatedBounds;
        }

        final ReferencedEnvelope envelope = ((BBoxMapBounds) rotatedBounds).toReferencedEnvelope(null);
        // the paint area size and the map bounds are rotated independently. because
        // the paint area size is rounded to integers, the map bounds have to be adjusted
        // to these rounding changes.
        final double widthRatio = paintArea.getWidth() / paintAreaPrecise.getWidth();
        final double heightRatio = paintArea.getHeight() / paintAreaPrecise.getHeight();

        final double adaptedWidth = envelope.getWidth() * widthRatio;
        final double adaptedHeight = envelope.getHeight() * heightRatio;

        final double widthDiff = adaptedWidth - envelope.getWidth();
        final double heigthDiff = adaptedHeight - envelope.getHeight();
        envelope.expandBy(widthDiff / 2.0, heigthDiff / 2.0);

        return new BBoxMapBounds(envelope);
    }

    /**
     * Return the map bounds rotated with the set rotation. The bounds are adapted to rounding changes of the
     * size of the set paint area.
     *
     * @return Rotated bounds.
     */
    public MapBounds getRotatedBoundsAdjustedForPreciseRotatedMapSize() {
        Rectangle2D.Double paintAreaPrecise = getRotatedMapSizePrecise();
        Rectangle paintArea = new Rectangle(MapfishMapContext.rectangleDoubleToDimension(paintAreaPrecise));
        return getRotatedBounds(paintAreaPrecise, paintArea);
    }

    public Dimension getMapSize() {
        return this.mapSize;
    }

    public Scale getScale() {
        return this.bounds.getScale(getPaintArea(), this.dpi);
    }

    public double getGeodeticScaleDenominator() {
        return getScale()
                .getGeodeticDenominator(this.bounds.getProjection(), this.dpi, getBounds().getCenter());
    }

    /**
     * Get a nicely rounded scale for to use for displaying the map scale.
     * 

* One of the output parameters of the {@link org.mapfish.print.processor.map.CreateMapProcessor} is * 'mapContext' which can be accessed in a template. If the scale is required in the template then it can * be accessed via: * $P{mapContext}.getRoundedScaleDenominator() *

*/ public double getRoundedScaleDenominator() { return getRoundedScaleDenominator(false); } /** * Get a nicely rounded scale for to use for displaying the map scale. *

* One of the output parameters of the {@link org.mapfish.print.processor.map.CreateMapProcessor} is * 'mapContext' which can be accessed in a template. If the scale is required in the template then it can * be accessed via: * $P{mapContext}.getRoundedScaleDenominato() *

* * @param geodetic Get geodetic scale */ public double getRoundedScaleDenominator(final boolean geodetic) { final double scaleDenominator = this.bounds.getScale(getPaintArea(), this.dpi) .getDenominator(geodetic, getBounds().getProjection(), this.dpi, getBounds().getCenter()); final int numChars = String.format("%d", Math.round(scaleDenominator)).length(); if (numChars > 2) { double factor = Math.pow(10, (numChars - 2)); return Math.round(scaleDenominator / factor) * factor; } else if (scaleDenominator > 1) { return Math.round(scaleDenominator); } return scaleDenominator; } /** * Utility method use to display the center in the report. * * @return the center X */ public double getCenterX() { return getCenter(0); } /** * Utility method use to display the center in the report. * * @return the center Y */ public double getCenterY() { return getCenter(1); } private double getCenter(final int dimension) { return getBounds().toReferencedEnvelope(new Rectangle(getMapSize())).getMedian(dimension); } /** * @return The new map size taking the rotation into account. */ public Dimension getRotatedMapSize() { return MapfishMapContext.rectangleDoubleToDimension(getRotatedMapSizePrecise()); } /** * @return The new map size taking the rotation into account. */ public Rectangle2D.Double getRotatedMapSizePrecise() { if (FloatingPointUtil.equals(this.rotation, 0.0)) { return new Rectangle2D.Double(0, 0, this.mapSize.getWidth(), this.mapSize.getHeight()); } final double rotatedWidth = getRotatedMapWidth(); final double rotatedHeight = getRotatedMapHeight(); return new Rectangle2D.Double(0, 0, rotatedWidth, rotatedHeight); } /** * Returns an {@link AffineTransform} taking the rotation into account. * * @return an affine transformation */ public AffineTransform getTransform() { if (FloatingPointUtil.equals(this.rotation, 0.0)) { return null; } final Dimension rotatedMapSize = getRotatedMapSize(); final AffineTransform transform = AffineTransform.getTranslateInstance(0.0, 0.0); // move to the center of the original map rectangle (this is the actual // size of the graphic) transform.translate(this.mapSize.width / 2, this.mapSize.height / 2); // then rotate around this center transform.rotate(this.rotation); // then move to an artificial origin (0,0) which might be outside of the actual // painting area. this origin still keeps the center of the original map area // at the center of the rotated map area. transform.translate(-rotatedMapSize.width / 2, -rotatedMapSize.height / 2); return transform; } public double getDPI() { return this.dpi; } private double getRotatedMapWidth() { double width = this.mapSize.getWidth(); if (!FloatingPointUtil.equals(this.rotation, 0.0)) { double height = this.mapSize.getHeight(); width = Math.abs(width * Math.cos(this.rotation)) + Math.abs(height * Math.sin(this.rotation)); } return width; } private double getRotatedMapHeight() { double height = this.mapSize.getHeight(); if (!FloatingPointUtil.equals(this.rotation, 0.0)) { double width = this.mapSize.getWidth(); height = Math.abs(height * Math.cos(this.rotation)) + Math.abs(width * Math.sin(this.rotation)); } return height; } public Rectangle getPaintArea() { return new Rectangle(this.mapSize); } @Nullable public Boolean isForceLongitudeFirst() { return this.forceLongitudeFirst; } public Boolean isDpiSensitiveStyle() { return this.dpiSensitiveStyle; } /** * Get the bounds as a referenced envelope. * * @return bounds as a referenced envelope. */ public ReferencedEnvelope toReferencedEnvelope() { return this.bounds.toReferencedEnvelope(getPaintArea()); } /** * Get the parent context if there is one. A parent context is the context that this context is derived * from. Normally there are some parameters that have been changed for this context from the parent. An * example of when there might be a parent is when the child has been rotated and has a bounds to envelope * the original bounds. It can be useful in some cases to be able to access the parent (and original * bounds). * * @return the parent context or null if there is no parent. */ @Nullable public MapfishMapContext getParentContext() { return this.parent; } /** * Return the root context which is this context or the context found by recursively calling * parent.getRootContext(). * * @return */ @Nonnull public MapfishMapContext getRootContext() { if (this.parent != null) { return this.parent.getRootContext(); } return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy