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

org.mapfish.print.Transformer Maven / Gradle / Ivy

/*
 * Copyright (C) 2013  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 .
 */

package org.mapfish.print;

import com.itextpdf.awt.geom.AffineTransform;

import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.CRS;
import org.mapfish.print.config.Config;
import org.geotools.referencing.GeodeticCalculator;
import org.mapfish.print.utils.DistanceUnit;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.itextpdf.text.pdf.PdfContentByte;

/**
 * Class that deals with the geometric tranformation between the geographic,
 * bitmap, and paper space for a map rendering.
 */
public class Transformer implements Cloneable {
    private static final String GOOGLE_WKT = "PROJCS[\"Google Mercator\","
                                             + "GEOGCS[\"WGS 84\","
                                             + "DATUM[\"World Geodetic System 1984\","
                                             + "SPHEROID[\"WGS 84\", 6378137.0, 298.257223563, AUTHORITY[\"EPSG\",\"7030\"]],"
                                             + "AUTHORITY[\"EPSG\",\"6326\"]],"
                                             + "PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]],"
                                             + "UNIT[\"degree\", 0.017453292519943295],"
                                             + "AXIS[\"Geodetic latitude\", NORTH],"
                                             + "AXIS[\"Geodetic longitude\", EAST],"
                                             + "AUTHORITY[\"EPSG\",\"4326\"]],"
                                             + "PROJECTION[\"Mercator_1SP\"],"
                                             + "PARAMETER[\"semi_minor\", 6378137.0],"
                                             + "PARAMETER[\"latitude_of_origin\", 0.0],"
                                             + "PARAMETER[\"central_meridian\", 0.0],"
                                             + "PARAMETER[\"scale_factor\", 1.0],"
                                             + "PARAMETER[\"false_easting\", 0.0],"
                                             + "PARAMETER[\"false_northing\", 0.0]," + "UNIT[\"m\", 1.0],"
                                             + "AXIS[\"Easting\", EAST]," + "AXIS[\"Northing\", NORTH],"
                                             + "AUTHORITY[\"EPSG\",\"900913\"]]";

    private float svgFactor = 1.0f;
    public double minGeoX;
    public double minGeoY;
    public double maxGeoX;
    public double maxGeoY;
    private final double scale;
    private final float paperWidth;
    private final float paperHeight;
    private double pixelPerGeoUnit;
    private float paperPosX;
    private float paperPosY;
    private final int dpi;

    /**
     * angle in radian
     */
    private double rotation;
    private final boolean strictEpsg4326;

    private void adjustSvgFactor(int dpi, boolean isIntegerSvg) {
        /**
         * The following code has been changed due to the fact that it seems
         * wrong. However, I'm not sure if my "correction" solves the problem
         * for the better. So, please review.
         *
         * The ('wrong') code below can be reformed: (600 + dpi - 1) / dpi = 1 +
         * 599/dpi and is never smaller than 1, since dpi>1. Also, it does not
         * make sense, as this factor gets smaller with increasing values for
         * dpi.
         */
        // target at least 600DPI for the SVG precision
        // svgFactor = Math.max((600 + dpi - 1) / dpi, 1);

        /**
         * svgFactor seems to not matter so much so we set it to 1 (above) needs
         * to get bigger if DPI increases and at standard 72 DPI needs to be 1.0
         */
        if (isIntegerSvg) { // integerSvg: true # in yaml
            // config file
            if (dpi < 600) { // target at least 600 DPI, this is a hack and only
                // needed for MapServer <= 5.6 where integers
                // are put into SVG
                svgFactor = 600f / 72.0f;
                /**
                 * = 8.33 so almost 9 as before with svgFactor being (600 + dpi
                 * -1)/dpi = ~9 if dpi = 72
                 */
            } else {
                svgFactor = dpi / 72.0f; // gets greater than 8.33
            }
        } // else defaults to 1.0 as it should with MapServer >= 6 and CAIRO SVG
        // rendering with floating point values
    }

    /**
     * @param centerX       geographic center in projection - x
     * @param centerY       geographic center in projection - y
     * @param paperWidth    e.g. map width in pt on the PDF
     * @param paperHeight   e.g. map height in pt on the PDF
     * @param scale         e.g. 10000 if scale is 1:10,000
     * @param dpi           as selected in request
     * @param unitEnum      the distance unit of the map e.g. DistanceUnit.M for meters
     * @param rotation      the rotation of the map per the request
     * @param geodeticSRS   if not null then it is a the srs to use with the geodetic
     * @param strictEpsg4326 if true then EPSG:4326 should interpretted as lat/long otherwise use the "incorrect" long/lat
     */
    public Transformer(double centerX, double centerY, float paperWidth,
                       float paperHeight, double scale, int dpi, DistanceUnit unitEnum,
                       double rotation, String geodeticSRS, boolean isIntegerSvg, boolean strictEpsg4326) {
        this.strictEpsg4326 = strictEpsg4326;
        this.dpi = dpi;
        pixelPerGeoUnit = (unitEnum.convertTo(dpi, DistanceUnit.IN) / scale);

        double geoWidth = paperWidth * dpi / 72.0f / pixelPerGeoUnit;
        double geoHeight = paperHeight * dpi / 72.0f / pixelPerGeoUnit;

        adjustSvgFactor(dpi, isIntegerSvg);

        this.paperWidth = paperWidth;
        this.paperHeight = paperHeight;
        this.scale = scale;
        this.rotation = rotation;

        if (geodeticSRS != null) {
            computeGeodeticBBox(geoWidth, geoHeight, centerX, centerY, dpi,
                    geodeticSRS);
        } else {
            this.minGeoX = centerX - (geoWidth / 2.0f);
            this.minGeoY = centerY - (geoHeight / 2.0f);
            this.maxGeoX = minGeoX + geoWidth;
            this.maxGeoY = minGeoY + geoHeight;
        }

    }

    private void computeGeodeticBBox(double geoWidth, double geoHeight,
                                     double centerX, double centerY, float dpi, String srsCode) {
        try {
            CoordinateReferenceSystem crs;
            if (srsCode.equalsIgnoreCase("EPSG:900913")) {
                crs = CRS.parseWKT(GOOGLE_WKT);
            } else {
                crs = CRS.decode(srsCode, !strictEpsg4326);
            }
            GeodeticCalculator calc = new GeodeticCalculator(crs);
            DirectPosition2D directPosition2D = new DirectPosition2D(centerX,
                    centerY);
            directPosition2D.setCoordinateReferenceSystem(crs);
            calc.setStartingPosition(directPosition2D);

            calc.setDirection(-90, geoWidth / 2.0f);
            minGeoX = (float) calc.getDestinationPosition().getOrdinate(0);

            calc.setDirection(90, geoWidth / 2.0f);
            maxGeoX = (float) calc.getDestinationPosition().getOrdinate(0);

            calc.setDirection(180, geoHeight / 2.0f);
            minGeoY = (float) calc.getDestinationPosition().getOrdinate(1);

            calc.setDirection(0, geoHeight / 2.0f);
            maxGeoY = (float) calc.getDestinationPosition().getOrdinate(1);

            pixelPerGeoUnit = (paperWidth * dpi) / 72.0f / (maxGeoX - minGeoX);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public double getGeoW() {
        return maxGeoX - minGeoX;
    }

    public double getGeoH() {
        return (maxGeoY - minGeoY);
    }

    public double getStraightBitmapW() {
        return getGeoW() * pixelPerGeoUnit;
    }

    public double getStraightBitmapH() {
        return getGeoH() * pixelPerGeoUnit;
    }

    public long getRotatedBitmapW() {
        double width = getStraightBitmapW();
        if (rotation != 0.0) {
            double height = getStraightBitmapH();
            width = Math.abs(width * Math.cos(rotation))
                    + Math.abs(height * Math.sin(rotation));
        }
        return Math.round(width);
    }

    public long getRotatedBitmapH() {
        double height = getStraightBitmapH();
        if (rotation != 0.0) {
            double width = getStraightBitmapW();
            height = Math.abs(height * Math.cos(rotation))
                     + Math.abs(width * Math.sin(rotation));
        }
        return Math.round(height);
    }

    public double getRotatedGeoW() {
        double width = getGeoW();
        if (rotation != 0.0) {
            double height = getGeoH();
            width = (float) (Math.abs(width * Math.cos(rotation)) + Math
                    .abs(height * Math.sin(rotation)));
        }
        return width;
    }

    public double getRotatedGeoH() {
        double height = getGeoH();
        if (rotation != 0.0) {
            double width = getGeoW();
            height = (float) (Math.abs(height * Math.cos(rotation)) + Math
                    .abs(width * Math.sin(rotation)));
        }
        return height;
    }

    public float getRotatedPaperW() {
        float width = getPaperW();
        if (rotation != 0.0) {
            float height = getPaperH();
            width = (float) (Math.abs(width * Math.cos(rotation)) + Math
                    .abs(height * Math.sin(rotation)));
        }
        return width;
    }

    public float getRotatedPaperH() {
        float height = getPaperH();
        if (rotation != 0.0) {
            float width = getPaperW();
            height = (float) (Math.abs(height * Math.cos(rotation)) + Math
                    .abs(width * Math.sin(rotation)));
        }
        return height;
    }

    public double getRotatedMinGeoX() {
        return minGeoX - (getRotatedGeoW() - getGeoW()) / 2.0F;
    }

    public double getRotatedMaxGeoX() {
        return maxGeoX + (getRotatedGeoW() - getGeoW()) / 2.0F;
    }

    public double getRotatedMinGeoY() {
        return minGeoY - (getRotatedGeoH() - getGeoH()) / 2.0F;
    }

    public double getRotatedMaxGeoY() {
        return maxGeoY + (getRotatedGeoH() - getGeoH()) / 2.0F;
    }

    public long getRotatedSvgW() {
        return (long) (getRotatedBitmapW() * svgFactor);
    }

    public long getRotatedSvgH() {
        return (long) (getRotatedBitmapH() * svgFactor);
    }

    public long getStraightSvgW() {
        return (long) (getStraightBitmapW() * svgFactor);
    }

    public long getStraightSvgH() {
        return (long) (getStraightBitmapH() * svgFactor);
    }

    public float getPaperW() {
        return paperWidth;
    }

    public float getPaperH() {
        return paperHeight;
    }

    public void setMapPos(float x, float y) {
        paperPosX = x;
        paperPosY = y;
    }

    public float getPaperPosX() {
        return paperPosX;
    }

    public float getPaperPosY() {
        return paperPosY;
    }

    /**
     * @return a transformer with paper dimensions, but that takes into account
     * the position of the map and its rotation.
     */
    public AffineTransform getBaseTransform() {
        final AffineTransform result = AffineTransform.getTranslateInstance(
                paperPosX, paperPosY);
        if (rotation != 0.0F) {
            result.translate(getPaperW() / 2, getPaperH() / 2);
            result.rotate(rotation);
            result.translate(-getRotatedPaperW() / 2, -getRotatedPaperH() / 2);
        }
        return result;
    }

    /**
     * @param reverseRotation True to do the rotation in the other direction
     * @return The affine transformation to go from geographic coordinated to
     * paper coordinates
     */
    public AffineTransform getGeoTransform(boolean reverseRotation) {
        final AffineTransform result = AffineTransform.getTranslateInstance(
                paperPosX, paperPosY);
        if (rotation != 0.0F) {
            result.rotate((reverseRotation ? -1 : 1) * rotation,
                    getPaperW() / 2, getPaperH() / 2);
        }
        result.scale(getPaperW() / getGeoW(), getPaperH() / getGeoH());
        result.translate(-minGeoX, -minGeoY);
        return result;
    }

    public AffineTransform getSvgTransform() {
        final AffineTransform result = getBaseTransform();
        result.scale(getPaperW() / getStraightSvgW(), getPaperH()
                                                      / getStraightSvgH());
        return result;
    }

    public AffineTransform getPdfTransform() {
        final AffineTransform result = getBaseTransform();
        result.scale(getPaperW() / getStraightBitmapW(), getPaperH()
                                                         / getStraightBitmapH());
        return result;
    }

    public AffineTransform getBitmapTransform() {
        return getPdfTransform();
    }

    public double getScale() {
        return scale;
    }

    public void zoom(Transformer mainTransformer, float factor) {
        double destW = mainTransformer.getGeoW() / factor;
        double destH = mainTransformer.getGeoH() / factor;

        // fix aspect ratio
        if (destW / destH > getGeoW() / getGeoH()) {
            destH = getGeoH() * destW / getGeoW();
        } else {
            destW = getGeoW() * destH / getGeoH();
        }

        double cX = (minGeoX + maxGeoX) / 2.0f;
        double cY = (minGeoY + maxGeoY) / 2.0f;
        pixelPerGeoUnit = pixelPerGeoUnit * getGeoW() / destW;
        minGeoX = cX - destW / 2.0f;
        maxGeoX = cX + destW / 2.0f;
        minGeoY = cY - destH / 2.0f;
        maxGeoY = cY + destH / 2.0f;
    }

    public Transformer clone() {
        try {
            return (Transformer) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public double getMinGeoX() {
        return minGeoX;
    }

    public double getMinGeoY() {
        return minGeoY;
    }

    public double getMaxGeoX() {
        return maxGeoX;
    }

    public double getMaxGeoY() {
        return maxGeoY;
    }

    public float getSvgFactor() {
        return svgFactor;
    }

    public double getRotation() {
        return rotation;
    }

    public void setClipping(PdfContentByte dc) {
        dc.rectangle(paperPosX, paperPosY, paperWidth, paperHeight);
        dc.clip();
        dc.newPath();
    }

    public void setRotation(double rotation) {
        this.rotation = rotation;
    }

    public double getResolution() {
        return 1 / pixelPerGeoUnit;
    }

    public void setResolution(double resolution) {
        this.pixelPerGeoUnit = 1 / resolution;
    }

    public int getDpi() {
        return dpi;
    }

    public boolean strictEpsg4326() {
        return this.strictEpsg4326;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy