/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008 - 2014, Geomatys * * This library 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; * version 2.1 of the License. * * This library 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. */ package org.geotoolkit.display2d.canvas; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.measure.quantity.Length; import javax.measure.Unit; import org.apache.sis.geometry.Envelope2D; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.util.logging.Logging; import org.geotoolkit.display.canvas.CanvasUtilities; import org.geotoolkit.display.canvas.RenderingContext; import org.geotoolkit.display.canvas.control.CanvasMonitor; import org.geotoolkit.display2d.GO2Hints; import org.geotoolkit.display2d.GO2Utilities; import org.geotoolkit.display2d.style.labeling.LabelRenderer; import org.geotoolkit.display2d.style.labeling.decimate.DecimationLabelRenderer; import org.geotoolkit.geometry.DefaultBoundingBox; import org.geotoolkit.geometry.jts.JTS; import org.geotoolkit.internal.referencing.CRSUtilities; import org.geotoolkit.referencing.ReferencingUtilities; import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.geotoolkit.resources.Errors; import org.opengis.geometry.BoundingBox; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.operation.TransformException; import org.opengis.util.FactoryException; import org.apache.sis.util.Utilities; import org.apache.sis.measure.Units; /** * Rendering Context for Java2D. * * @author Johann Sorel (Geomatys) * @module */ public class RenderingContext2D implements RenderingContext{ private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.display2d.canvas"); private static final int MAX_WRAP = 3; private static final Map<Font,FontMetrics> fontMetrics = new HashMap<>(); private static final int DISPLAY_TRS = 0; private static final int OBJECTIVE_TRS = 1; private static final int OTHER_TRS = 2; private int current = DISPLAY_TRS; public final GeometryFactory GF = new GeometryFactory(); /** * The originating canvas. */ private final J2DCanvas canvas; /** * The graphics handle to use for painting. This graphics is set by {@link BufferedCanvas2D} * when a new painting in underway. It is reset to {@code null} once the rendering is finished. * * @see #getGraphics */ private Graphics2D graphics = null; /* * cache of the Graphics2D rendering hints. */ private RenderingHints renderingHints = null; private double dpi = 90; /** * A snapshot of {@link ReferencedCanvas#getObjectiveCRS} at the time of painting. This is the * "real world" coordinate reference system that the user will see on the screen. Data from all * {@link GraphicPrimitive2D} must be transformed to this CRS before to be painted. Units are * usually "real world" metres. * <p> * This coordinate system is usually set once for a given {@link BufferedCanvas2D} and do not * change anymore, except if the user wants to change the projection see on screen. * * @see #displayCRS * @see #setGraphicsCRS * @see ReferencedCanvas#getObjectiveCRS */ private CoordinateReferenceSystem objectiveCRS = null; private CoordinateReferenceSystem objectiveCRS2D = null; /** * A snapshot of {@link ReferencedCanvas#getDisplayCRS} at the time of painting. This CRS maps * the {@linkplain Graphics2D user space} in terms of <cite>Java2D</cite>: each "unit" is a dot * (about 1/72 of inch). <var>x</var> values increase toward the right of the screen and * <var>y</var> values increase toward the bottom of the screen. This CRS is appropriate * for rendering text and labels. * <p> * This coordinate system may be different between two different renderings, * especially if the zoom (or map scale) has changed since the last rendering. * * @see #objectiveCRS * @see #setGraphicsCRS * @see ReferencedCanvas#getDisplayCRS */ private CoordinateReferenceSystem displayCRS = null; private CanvasMonitor monitor = null; private AffineTransform2D objectiveToDisplay = null; private AffineTransform2D displayToObjective = null; /** * Multiple repetition if there is a world wrap */ public RenderingWrapParams wraps = null; /** * The affine transform from {@link #objectiveCRS} to {@code deviceCRS}. Used by * {@link #setGraphicsCRS} when the CRS is {@link #objectiveCRS}. This is a pretty common case, * and unfortunately one that is badly optimized by {@link ReferencedCanvas#getMathTransform}. */ private AffineTransform objectiveToDevice = null; /** * The affine transform from {@link #displayCRS} to {@code deviceCRS}. * Used by {@link #setGraphicsCRS} when the CRS is {@link #displayCRS}. */ private AffineTransform displayToDevice = null; /** * The label renderer. Shall be created only once. */ private LabelRenderer labelRenderer = null; /** * List of coefficients from "Unit" to Objective CRS. */ private final Map<Unit<Length>,Float> coeffs = new IdentityHashMap<>(); /** * Precalculated resolution, avoid graphics to recalculate it since */ private double[] resolution; /** * Precalculated geographic scale, avoid graphics to recalculate it. */ private double geoScale = 1; /** * Precaculated geographic scale calculated using OGC Symbology Encoding * Specification. * This is not the scale Objective to Display. * This is not an accurate geographic scale. * This is a fake average scale unproper for correct rendering. * It is used only to filter SE rules. */ private double seScale = 1; private final Date[] temporalRange = new Date[2]; private final Double[] elevationRange = new Double[2]; private Shape paintingDisplayShape = null; private Rectangle paintingDisplaybounds = null; private Shape paintingObjectiveShape = null; private Envelope paintingObjectiveBBox = null; private Envelope paintingObjectiveBBox2D = null; private BoundingBox paintingObjectiveBBox2DB = null; private Shape canvasDisplayShape = null; private Rectangle canvasDisplaybounds = null; private Shape canvasObjectiveShape = null; private Envelope canvasObjectiveBBox = null; private Envelope canvasObjectiveBBox2D = null; private BoundingBox canvasObjectiveBBox2DB = null; /** * Constructs a new {@code RenderingContext} for the specified canvas. * * @param canvas The canvas which creates this rendering context. */ public RenderingContext2D(final J2DCanvas canvas) { this.canvas = canvas; } public void initParameters(final AffineTransform2D objToDisp, final CanvasMonitor monitor, final Shape paintingDisplayShape, final Shape paintingObjectiveShape, final Shape canvasDisplayShape, final Shape canvasObjectiveShape, final double dpi){ this.canvasObjectiveBBox= canvas.getVisibleEnvelope(); this.objectiveCRS = canvasObjectiveBBox.getCoordinateReferenceSystem(); this.objectiveCRS2D = canvas.getObjectiveCRS2D(); this.displayCRS = canvas.getDisplayCRS(); this.objectiveToDisplay = objToDisp; try { this.displayToObjective = (AffineTransform2D) objToDisp.inverse(); } catch (NoninvertibleTransformException ex) { LOGGER.log(Level.WARNING, null, ex); } this.monitor = monitor; this.labelRenderer = null; this.coeffs.clear(); //set the Pixel coeff = 1 this.coeffs.put(Units.POINT, 1f); //calculate canvas shape/bounds values --------------------------------- this.canvasDisplayShape = canvasDisplayShape; final Rectangle2D canvasDisplayBounds = canvasDisplayShape.getBounds2D(); this.canvasDisplaybounds = canvasDisplayBounds.getBounds(); this.canvasObjectiveShape = canvasObjectiveShape; final Rectangle2D canvasObjectiveBounds = canvasObjectiveShape.getBounds2D(); //calculate the objective bbox with there temporal and elevation parameters ---- this.canvasObjectiveBBox2D = new Envelope2D(objectiveCRS2D,canvasObjectiveBounds); this.canvasObjectiveBBox2DB = new DefaultBoundingBox(canvasObjectiveBBox2D); //calculate the resolution ----------------------------------------------- this.dpi = dpi; this.resolution = new double[2]; //-- explicitely exprime resolution only into multidimensional CRS horizontal 2D part this.resolution[0] = canvasObjectiveBounds.getWidth()/canvasDisplayBounds.getWidth(); this.resolution[1] = canvasObjectiveBounds.getHeight()/canvasDisplayBounds.getHeight(); adjustResolutionWithDPI(resolution); //calculate painting shape/bounds values ------------------------------- this.paintingDisplayShape = paintingDisplayShape; final Rectangle2D paintingDisplayBounds = paintingDisplayShape.getBounds2D(); this.paintingDisplaybounds = paintingDisplayBounds.getBounds(); this.paintingObjectiveShape = paintingObjectiveShape; final Rectangle2D paintingObjectiveBounds = paintingObjectiveShape.getBounds2D(); this.paintingObjectiveBBox2D = new Envelope2D(objectiveCRS2D,paintingObjectiveBounds); this.paintingObjectiveBBox2DB = new DefaultBoundingBox(paintingObjectiveBBox2D); this.paintingObjectiveBBox = new GeneralEnvelope(canvasObjectiveBBox); ((GeneralEnvelope)this.paintingObjectiveBBox).setRange(0, paintingObjectiveBounds.getMinX(), paintingObjectiveBounds.getMaxX()); ((GeneralEnvelope)this.paintingObjectiveBBox).setRange(1, paintingObjectiveBounds.getMinY(), paintingObjectiveBounds.getMaxY()); try { geoScale = canvas.getGeographicScale(); } catch (TransformException ex) { //could not calculate the geographic scale. geoScale = 1; LOGGER.log(Level.WARNING, null, ex); } //set temporal and elevation range-------------------------------------- final Date[] temporal = canvas.getTemporalRange(); if(temporal != null){ temporalRange[0] = temporal[0]; temporalRange[1] = temporal[1]; }else{ Arrays.fill(temporalRange, null); } final Double[] elevation = canvas.getElevationRange(); if(elevation != null){ elevationRange[0] = elevation[0]; elevationRange[1] = elevation[1]; }else{ Arrays.fill(elevationRange, null); } //calculate the symbology encoding scale ------------------------------- seScale = CanvasUtilities.computeSEScale( getCanvasObjectiveBounds2D(), getObjectiveToDisplay(), getCanvasDisplayBounds()); //prepare informations for possible map repetition --------------------- wraps = null; try { //test if wrap is possible final DirectPosition[] wrapPoints = ReferencingUtilities.findWrapAround(objectiveCRS2D); if(wrapPoints != null){ //search the multiples transformations wraps = new RenderingWrapParams(); wraps.wrapPoints = wrapPoints; //project the 4 canvas bounds points on the wrap line final double[] projs = new double[8]; projs[0] = canvasDisplaybounds.x; projs[1] = canvasDisplaybounds.y; projs[2] = canvasDisplaybounds.x+canvasDisplaybounds.width; projs[3] = canvasDisplaybounds.y; projs[4] = canvasDisplaybounds.x+canvasDisplaybounds.width; projs[5] = canvasDisplaybounds.y+canvasDisplaybounds.height; projs[6] = canvasDisplaybounds.x; projs[7] = canvasDisplaybounds.y+canvasDisplaybounds.height; displayToObjective.transform(projs, 0, projs, 0, 4); final double x1 = wrapPoints[0].getOrdinate(0); final double y1 = wrapPoints[0].getOrdinate(1); final double x2 = wrapPoints[1].getOrdinate(0); final double y2 = wrapPoints[1].getOrdinate(1); nearestColinearPoint(x1, y1, x2, y2, projs, 0); nearestColinearPoint(x1, y1, x2, y2, projs, 2); nearestColinearPoint(x1, y1, x2, y2, projs, 4); nearestColinearPoint(x1, y1, x2, y2, projs, 6); final Point2D.Double p0 = new Point2D.Double(x1, y1); final Point2D.Double p1 = new Point2D.Double(x2, y2); //test projected points final Point2D.Double refVector = toVector(p0, p1); double kLeft = 0; double kRight = 0; for(int i=0;i<8;i+=2){ final Point2D.Double candidate = new Point2D.Double(projs[i], projs[i+1]); final Point2D.Double candidateVector = toVector(p0, candidate); double k = refVector.x*candidateVector.x + refVector.y*candidateVector.y; k /= (refVector.x*refVector.x) + (refVector.y*refVector.y); if(k<0){ if(kLeft==0){ kLeft = -k; }else{ kLeft = Math.max(kLeft,-k); } }else if(k>1){ k -=1; if(kRight==0){ kRight = k; }else{ kRight = Math.max(kRight,k); } } } int nbLeft = (int) Math.ceil(kLeft); int nbRight = (int) Math.ceil(kRight); if(nbLeft<0)nbLeft=0; if(nbRight<0)nbRight=0; if(nbLeft >MAX_WRAP) nbLeft = MAX_WRAP; if(nbRight>MAX_WRAP) nbRight = MAX_WRAP; wraps.wrapDecNb = nbLeft; wraps.wrapIncNb = nbRight; //increment by one for possible geometry overlaping the meridian //those will need and extra repetition nbLeft++; nbRight++; //normal transforms wraps.wrapObj = new AffineTransform2D(new AffineTransform()); wraps.wrapObjToDisp = objToDisp; //decreasing and increasing wraps wraps.wrapDecObjToDisp = new AffineTransform2D[nbLeft]; wraps.wrapDecObj = new AffineTransform2D[nbLeft]; wraps.wrapIncObjToDisp = new AffineTransform2D[nbRight]; wraps.wrapIncObj = new AffineTransform2D[nbRight]; final AffineTransform dif = new AffineTransform(); final AffineTransform step = new AffineTransform(objToDisp); dif.setToTranslation(x2-x1,y2-y1); for(int i=0;i<nbRight;i++){ step.concatenate(dif); wraps.wrapIncObj[i] = new AffineTransform2D(1, 0, 0, 1, (x2-x1)*(i+1), (y2-y1)*(i+1)); wraps.wrapIncObjToDisp[i] = new AffineTransform2D(step); } step.setTransform(objToDisp); dif.setToTranslation(x1-x2,y1-y2); for(int i=0;i<nbLeft;i++){ step.concatenate(dif); wraps.wrapDecObj[i] = new AffineTransform2D(1, 0, 0, 1, (x1-x2)*(i+1), (y1-y2)*(i+1)); wraps.wrapDecObjToDisp[i] = new AffineTransform2D(step); } //build the wrap rectangle final GeneralEnvelope env = new GeneralEnvelope(objectiveCRS2D); env.add(wrapPoints[0]); env.add(wrapPoints[1]); if(env.getSpan(0) == 0){ final double min = canvasObjectiveBBox2D.getMinimum(0); final double max = canvasObjectiveBBox2D.getMaximum(0); env.setRange(0, min, max); wraps.wrapDecLine = GF.createLineString(new Coordinate[]{ new Coordinate(min, env.getMinimum(1)), new Coordinate(max, env.getMinimum(1))}); wraps.wrapIncLine = GF.createLineString(new Coordinate[]{ new Coordinate(min, env.getMaximum(1)), new Coordinate(max, env.getMaximum(1))}); }else{ final double min = canvasObjectiveBBox2D.getMinimum(1); final double max = canvasObjectiveBBox2D.getMaximum(1); env.setRange(1, min, max); wraps.wrapDecLine = GF.createLineString(new Coordinate[]{ new Coordinate(env.getMinimum(0), min), new Coordinate(env.getMinimum(0), max)}); wraps.wrapIncLine = GF.createLineString(new Coordinate[]{ new Coordinate(env.getMaximum(0), min), new Coordinate(env.getMaximum(0), max)}); } wraps.wrapArea = (com.vividsolutions.jts.geom.Polygon)JTS.toGeometry(env); wraps.objectiveJTSEnvelope = new com.vividsolutions.jts.geom.Envelope( canvasObjectiveBounds.getMinX(),canvasObjectiveBounds.getMaxX(), canvasObjectiveBounds.getMinY(),canvasObjectiveBounds.getMaxY()); //fix the envelopes, normalize them using wrap infos canvasObjectiveBBox = ReferencingUtilities.wrapNormalize(canvasObjectiveBBox, wrapPoints); canvasObjectiveBBox2D = ReferencingUtilities.wrapNormalize(canvasObjectiveBBox2D, wrapPoints); canvasObjectiveBBox2DB = new DefaultBoundingBox(canvasObjectiveBBox2D); paintingObjectiveBBox = ReferencingUtilities.wrapNormalize(paintingObjectiveBBox, wrapPoints); paintingObjectiveBBox2D = ReferencingUtilities.wrapNormalize(paintingObjectiveBBox2D, wrapPoints); paintingObjectiveBBox2DB = new DefaultBoundingBox(paintingObjectiveBBox2D); } } catch (TransformException ex) { LOGGER.log(Level.INFO, ex.getLocalizedMessage(), ex); } } private static Point2D.Double toVector(Point2D.Double p0, Point2D.Double p1){ return new Point2D.Double(p1.x-p0.x, p1.y-p1.y); } public static void nearestColinearPoint(final double x1, final double y1, final double x2, final double y2, final double[] point, int offset) { double x = point[offset]; double y = point[offset+1]; final double slope = (y2-y1) / (x2-x1); if (!Double.isInfinite(slope)) { final double y0 = (y2 - slope*x2); x = ((y-y0)*slope+x) / (slope*slope+1); y = x*slope + y0; } else { x = x2; } point[offset] = x; point[offset+1] = y; } public void initGraphic(final Graphics2D graphics){ this.graphics = graphics; this.renderingHints = graphics.getRenderingHints(); this.displayToDevice = (graphics != null) ? graphics.getTransform() : null; this.objectiveToDevice = (displayToDevice != null) ? new AffineTransform(displayToDevice) : new AffineTransform(); this.objectiveToDevice.concatenate(objectiveToDisplay); this.current = DISPLAY_TRS; } public void reset(){ this.coeffs.clear(); this.canvasDisplaybounds = null; this.displayCRS = null; this.canvasDisplayShape = null; this.displayToDevice = null; this.graphics = null; this.renderingHints = null; this.labelRenderer = null; this.monitor = null; this.canvasObjectiveBBox = null; this.objectiveCRS = null; this.canvasObjectiveShape = null; this.objectiveToDevice = null; this.objectiveToDisplay = null; this.resolution = null; this.current = DISPLAY_TRS; } public void dispose(){ if(graphics != null){ graphics.dispose(); } reset(); } /** * {@inheritDoc } */ @Override public J2DCanvas getCanvas(){ return canvas; } /** * {@inheritDoc } */ @Override public CoordinateReferenceSystem getObjectiveCRS() { return objectiveCRS; } /** * {@inheritDoc } */ @Override public CoordinateReferenceSystem getObjectiveCRS2D() { return objectiveCRS2D; } /** * {@inheritDoc } */ @Override public CoordinateReferenceSystem getDisplayCRS() { return displayCRS; } /** * Returns the graphics where painting occurs. The initial coordinate reference system is * {@link #getDisplayCRS()}, which maps the <cite>Java2D</cite> {@linkplain Graphics2D user space}. * For drawing shapes directly in terms of "real world" coordinates, users should invoke * <code>{@linkplain #setGraphicsCRS setGraphicsCRS}({@linkplain #getObjectiveCRS()})</code> or * {@linkplain #switchToDisplayCRS() } and {@linkplain #switchToObjectiveCRS() }. * @return Graphics2D */ public final Graphics2D getGraphics() { return graphics; } /** * Shortcut method which equals a call to : * context.setGraphicsCRS(context.getDisplayCRS); * without the need of a try catch. * * Optimization for a pretty common case. */ public void switchToDisplayCRS() { if(current != DISPLAY_TRS){ graphics.setTransform(displayToDevice); current = DISPLAY_TRS; } } /** * Shortcut method which equals a call to : * context.setGraphicsCRS(context.getObjectiveCRS); * whitout the need of a try catch. * * Optimization for a pretty common case. */ public void switchToObjectiveCRS() { if(current != OBJECTIVE_TRS){ graphics.setTransform(objectiveToDevice); current = OBJECTIVE_TRS; } } /** * {@inheritDoc } */ @Override public void setGraphicsCRS(CoordinateReferenceSystem crs) throws TransformException { if (crs == displayCRS) { switchToDisplayCRS(); }else if (crs == objectiveCRS || crs == objectiveCRS2D) { switchToObjectiveCRS(); } else try { crs = CRSUtilities.getCRS2D(crs); AffineTransform at = getAffineTransform(crs, displayCRS); at.preConcatenate(displayToDevice); current = OTHER_TRS; graphics.setTransform(at); } catch (FactoryException e) { throw new TransformException(Errors.format( Errors.Keys.IllegalCoordinateReferenceSystem), e); } } /** * {@inheritDoc } */ @Override public AffineTransform getAffineTransform(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) throws FactoryException { final MathTransform mt = canvas.getMathTransform(sourceCRS, targetCRS, RenderingContext2D.class, "getAffineTransform"); try { return (AffineTransform) mt; } catch (ClassCastException cause) { throw new FactoryException(Errors.format(Errors.Keys.NotAnAffineTransform), cause); } } /** * {@inheritDoc } */ @Override public MathTransform getMathTransform(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) throws FactoryException { return canvas.getMathTransform(sourceCRS, targetCRS, RenderingContext2D.class, "getMathTransform"); } /** * Like the Graphics class, this create method makes a clone of the current * Rendering context. this may be use in multithread to avoid several object to work * on the same context. * @param g2d Graphics2D * @return RenderingContext2D */ public RenderingContext2D create(final Graphics2D g2d){ final RenderingContext2D context = new RenderingContext2D(canvas); context.initParameters(objectiveToDisplay, monitor, paintingDisplayShape, paintingObjectiveShape, canvasDisplayShape, canvasObjectiveShape, dpi); context.initGraphic(g2d); g2d.setRenderingHints(this.graphics.getRenderingHints()); context.labelRenderer = getLabelRenderer(true); return context; } /** * Get or Create a label renderer for this rendering context. * @param create : if true will create a label renderer if there is none. * @return FontMetrics */ public LabelRenderer getLabelRenderer(final boolean create) { if(labelRenderer == null && create){ Class candidate = (Class)canvas.getRenderingHint(GO2Hints.KEY_LABEL_RENDERER_CLASS); if(candidate != null && LabelRenderer.class.isAssignableFrom(candidate)){ try { labelRenderer = (LabelRenderer) candidate.newInstance(); labelRenderer.setRenderingContext(this); } catch (InstantiationException | IllegalAccessException ex) { LOGGER.log(Level.WARNING, null, ex); } }else{ labelRenderer = new DecimationLabelRenderer(); labelRenderer.setRenderingContext(this); } } return labelRenderer; } /** * {@inheritDoc } */ @Override public CanvasMonitor getMonitor() { return monitor; } // Informations related to scale datas ------------------------------------- public FontMetrics getFontMetrics(Font f) { FontMetrics fm = fontMetrics.get(f); if(fm == null){ fm = getGraphics().getFontMetrics(f); fontMetrics.put(f, fm); } return fm; } /** * Find the coefficient between the given Unit and the Objective CRS. * @param unit * @return float */ public float getUnitCoefficient(final Unit<Length> uom){ Float f = coeffs.get(uom); if(f==null){ f = GO2Utilities.calculateScaleCoefficient(this,uom); coeffs.put(uom, f); } return f; } public double getDPI() { return dpi; } /** * Returns the current painting resolution. this may be used in the parameters * given to gridCoverageReaders to extract the best resolution grid coverage. * This resolution is between Objective and Display CRS. * * @return double[] of 2 dimensions */ public double[] getResolution() { return resolution.clone(); } /** * Returns the current painting resolution. this may be used in the parameters * given to gridCoverageReaders to extract the best resolution grid coverage. * This resolution is between the given CRS and Display CRS. * * @param crs * @return double[] of 2 dimensions */ public double[] getResolution(final CoordinateReferenceSystem crs) { if (Utilities.equalsIgnoreMetadata(objectiveCRS, crs)) { return getResolution(); } else { final double[] newRes = new double[2]; assert resolution.length == 2 : "RenderingContext2D : Resolution array should have length equals to 2. Founded length : "+resolution.length; assert Utilities.equalsIgnoreMetadata(canvasObjectiveBBox2D.getCoordinateReferenceSystem(), objectiveCRS2D) : "RenderingContext2D : canvasObjectiveBBox2D should own same CRS than objectiveCRS2D"; try { final CoordinateReferenceSystem target2DCRS = CRSUtilities.getCRS2D(crs); ReferencingUtilities.convertResolution(canvasObjectiveBBox2D, resolution, target2DCRS, newRes); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } return adjustResolutionWithDPI(newRes); } } /** * Adjust the resolution relative to 90 DPI. * a dpi under 90 with raise the resolution level while * a bigger spi will lower the resolution level. */ private double[] adjustResolutionWithDPI(final double[] res){ res[0] = (90/dpi) * res[0]; res[1] = (90/dpi) * res[1]; return res; } /** * Returns the scale factor, or {@link Double#NaN NaN} if the scale is unknow. The scale factor * is usually smaller than 1. For example for a 1:1000 scale, the scale factor will be 0.001. * This scale factor takes in account the physical size of the rendering device (e.g. the * screen size) if such information is available. Note that this scale can't be more accurate * than the {@linkplain java.awt.GraphicsConfiguration#getNormalizingTransform() information * supplied by the underlying system}. * * @return The rendering scale factor as a number between 0 and 1, or {@link Double#NaN}. * @see CanvasController2D#getScale() */ public double getScale() { return canvas.getScale(); } /** * Returns the geographic scale, like we can see in scalebar legends '1 : 200 000' * This is mainly used in style rules to check the minimum and maximum scales. * @return */ public double getGeographicScale() { return geoScale; } /** * Geographic scale calculated using OGC Symbology Encoding specification. * This is not the scale Objective to Display. * This is not an accurate geographic scale. * This is a fake average scale unproper for correct rendering. * It is used only to filter SE rules. * @return */ public double getSEScale() { return seScale; } /** * @return affine transform from objective CRS to display CRS. */ public AffineTransform2D getObjectiveToDisplay() { return objectiveToDisplay; } /** * @return affine transform from display CRS to objective CRS. */ public AffineTransform2D getDisplayToObjective() { return displayToObjective; } @Override public Date[] getTemporalRange() { return temporalRange; } @Override public Double[] getElevationRange() { return elevationRange; } // Informations about the currently painted area --------------------------- /** * {@inheritDoc } */ public Shape getPaintingDisplayShape(){ return paintingDisplayShape; } /** * {@inheritDoc } */ public Rectangle getPaintingDisplayBounds(){ return paintingDisplaybounds; } /** * {@inheritDoc } */ public Shape getPaintingObjectiveShape(){ return paintingObjectiveShape; } /** * {@inheritDoc } */ public BoundingBox getPaintingObjectiveBounds2D(){ return paintingObjectiveBBox2DB; } /** * {@inheritDoc } */ public Envelope getPaintingObjectiveBounds(){ return paintingObjectiveBBox; } // Informations about the complete canvas area ----------------------------- /** * {@inheritDoc } */ public Shape getCanvasDisplayShape() { return canvasDisplayShape; } /** * {@inheritDoc } */ public Rectangle getCanvasDisplayBounds() { return canvasDisplaybounds; } /** * {@inheritDoc } */ public Shape getCanvasObjectiveShape() { return canvasObjectiveShape; } /** * {@inheritDoc } */ public BoundingBox getCanvasObjectiveBounds2D() { return canvasObjectiveBBox2DB; } /** * {@inheritDoc } */ public Envelope getCanvasObjectiveBounds() { return canvasObjectiveBBox; } /** * Use this methods rather send getGraphics().getRenderingHints. * This method cases the result for better performances. * @return rendering hints of the graphics 2D. */ public RenderingHints getRenderingHints() { return renderingHints; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("========== Rendering Context 2D ==========\n"); sb.append("---------- Coordinate Reference Systems ----------\n"); sb.append("Objective CRS = \n"); sb.append(objectiveCRS).append("\n"); sb.append("Objective CRS 2D = \n"); sb.append(objectiveCRS2D).append("\n"); sb.append("Display CRS = \n"); sb.append(displayCRS).append("\n"); if(resolution != null){ sb.append("Resolution = "); for(double d : resolution){ sb.append(d).append(" "); } } sb.append("\n"); sb.append("Geographic Scale = "); sb.append(geoScale).append("\n"); sb.append("OGC SE Scale = "); sb.append(seScale).append("\n"); sb.append("Temporal range = "); sb.append(temporalRange[0]).append(" to ").append(temporalRange[1]).append("\n"); sb.append("Elevation range = "); sb.append(elevationRange[0]).append(" to ").append(elevationRange[1]).append("\n"); sb.append("\n---------- Canvas Geometries ----------\n"); sb.append("Display Shape = \n"); sb.append(canvasDisplayShape).append("\n"); sb.append("Display Bounds = \n"); sb.append(canvasDisplaybounds).append("\n"); sb.append("Objective Shape = \n"); sb.append(canvasObjectiveShape).append("\n"); sb.append("Objective BBOX = \n"); sb.append(canvasObjectiveBBox).append("\n"); sb.append("Objective BBOX 2D = \n"); sb.append(canvasObjectiveBBox2D).append("\n"); sb.append("\n---------- Painting Geometries (dirty area) ----------\n"); sb.append("Display Shape = \n"); sb.append(paintingDisplayShape).append("\n"); sb.append("Display Bounds = \n"); sb.append(paintingDisplaybounds).append("\n"); sb.append("Objective Shape = \n"); sb.append(paintingObjectiveShape).append("\n"); sb.append("Objective BBOX = \n"); sb.append(paintingObjectiveBBox).append("\n"); sb.append("Objective BBOX 2D = \n"); sb.append(paintingObjectiveBBox2D).append("\n"); sb.append("\n---------- Transforms ----------\n"); sb.append("Objective to Display = \n"); sb.append(objectiveToDisplay).append("\n"); sb.append("Display to Objective = \n"); sb.append(displayToObjective).append("\n"); sb.append("\n---------- Rendering Hints ----------\n"); if(renderingHints != null){ for(Entry<Object,Object> entry : renderingHints.entrySet()){ sb.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); } } sb.append("========== Rendering Context 2D ==========\n"); return sb.toString(); } }