/* * Copyright (C) 2009 Camptocamp * * This file is part of MapFish Server * * MapFish Server is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MapFish Server 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with MapFish Server. If not, see <http://www.gnu.org/licenses/>. */ package org.mapfish.print; import java.awt.geom.AffineTransform; import org.geotools.geometry.DirectPosition2D; import org.geotools.referencing.CRS; import org.geotools.referencing.GeodeticCalculator; import org.mapfish.print.utils.DistanceUnit; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.lowagie.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 int svgFactor; public float minGeoX; public float minGeoY; public float maxGeoX; public float maxGeoY; private final int scale; private final int paperWidth; private final int paperHeight; private float pixelPerGeoUnit; private float paperPosX; private float paperPosY; private final int dpi; /** * angle in radian */ private double rotation; /** * @param geodeticSRS * if not null then it is a the srs to use with the geodetic * calculator. if null it is assumed that it is non-geodetic */ public Transformer(float centerX, float centerY, int paperWidth, int paperHeight, int scale, int dpi, DistanceUnit unitEnum, double rotation, String geodeticSRS) { this.dpi = dpi; pixelPerGeoUnit = (float) (unitEnum.convertTo(dpi, DistanceUnit.IN) / scale); float geoWidth = paperWidth * dpi / 72.0f / pixelPerGeoUnit; float geoHeight = paperHeight * dpi / 72.0f / pixelPerGeoUnit; //target at least 600DPI for the SVG precision svgFactor = Math.max((600 + dpi - 1) / dpi, 1); 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(float geoWidth, float geoHeight, float centerX, float centerY, float dpi, String srsCode) { try { CoordinateReferenceSystem crs; if (srsCode.equalsIgnoreCase("EPSG:900913")) { crs = CRS.parseWKT(GOOGLE_WKT); } else { crs = CRS.decode(srsCode, true); } 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 float getGeoW() { return maxGeoX - minGeoX; } public float getGeoH() { return (maxGeoY - minGeoY); } public float getStraightBitmapW() { return getGeoW() * pixelPerGeoUnit; } public float 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 float getRotatedGeoW() { float width = getGeoW(); if (rotation != 0.0) { float height = getGeoH(); width = (float) (Math.abs(width * Math.cos(rotation)) + Math.abs(height * Math.sin(rotation))); } return width; } public float getRotatedGeoH() { float height = getGeoH(); if (rotation != 0.0) { float 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 float getRotatedMinGeoX() { return minGeoX - (getRotatedGeoW() - getGeoW()) / 2.0F; } public float getRotatedMaxGeoX() { return maxGeoX + (getRotatedGeoW() - getGeoW()) / 2.0F; } public float getRotatedMinGeoY() { return minGeoY - (getRotatedGeoH() - getGeoH()) / 2.0F; } public float getRotatedMaxGeoY() { return maxGeoY + (getRotatedGeoH() - getGeoH()) / 2.0F; } public long getRotatedSvgW() { return getRotatedBitmapW() * svgFactor; } public long getRotatedSvgH() { return 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 int getScale() { return scale; } public void zoom(Transformer mainTransformer, float factor) { float destW = mainTransformer.getGeoW() / factor; float destH = mainTransformer.getGeoH() / factor; //fix aspect ratio if (destW / destH > getGeoW() / getGeoH()) { destH = getGeoH() * destW / getGeoW(); } else { destW = getGeoW() * destH / getGeoH(); } float cX = (minGeoX + maxGeoX) / 2.0f; float 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 float getMinGeoX() { return minGeoX; } public float getMinGeoY() { return minGeoY; } public float getMaxGeoX() { return maxGeoX; } public float getMaxGeoY() { return maxGeoY; } public int 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 float getResolution() { return 1 / pixelPerGeoUnit; } public void setResolution(float resolution) { this.pixelPerGeoUnit = 1 / resolution; } public int getDpi() { return dpi; } }