/* gvSIG. Sistema de Informaci�n Geogr�fica de la Generalitat Valenciana
*
* Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,USA.
*
* For more information, contact:
*
* Generalitat Valenciana
* Conselleria d'Infraestructures i Transport
* Av. Blasco Ib��ez, 50
* 46010 VALENCIA
* SPAIN
*
* +34 963862235
* gvsig@gva.es
* www.gvsig.gva.es
*
* or
*
* IVER T.I. S.A
* Salamanca 50
* 46005 Valencia
* Spain
*
* +34 963163400
* dac@iver.es
*/
package com.iver.cit.gvsig.fmap;
import geomatico.events.EventBus;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.prefs.Preferences;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.renderer.lite.RendererUtilities;
import org.gvsig.main.events.ExtentChangeEvent;
import org.gvsig.map.MapContext;
import org.gvsig.units.Unit;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.operation.TransformException;
import com.iver.utiles.XMLEntity;
/**
* <p>
* <code>ViewPort</code> class represents the logic needed to transform a
* rectangular area of a map to the available area in screen to display it.
* </p>
*
* <p>
* Includes an affine transformation, between the rectangular area selected of
* the external map, in its own <i>map coordinates</i>, to the rectangular area
* available of a view in <i>screen coordinates</i>.
* </p>
*
* <p>
* Elements:
* <ul>
* <li><i>extent</i>: the area selected of the map, in <i>map coordinates</i>.
* <li><i>imageSize</i>: width and height in pixels (<i>screen coordinates</i>)
* of the area available in screen to display the area selected of the map.
* <li><i>adjustedExtent</i>: the area selected must be an scale of
* <i>imageSize</i>.<br>
* This implies adapt the extent, preserving and centering it, and adding around
* the needed area to fill all the image size. That added area will be extracted
* from the original map, wherever exists, and filled with the background color
* wherever not.
* <li><i>scale</i>: the scale between the adjusted extent and the image size.
* <li><i>backColor</i>: the default background color in the view, if there is
* no map.
* <li><i>trans</i>: the affine transformation.
* <li><i>proj</i>: map projection used in this view.
* <li><i>distanceUnits</i>: distance measurement units, of data in screen.
* <li><i>mapUnits</i>: measurement units, of data in map.
* <li><i>extents</i>: an {@link ExtentHistory ExtentHistory} with the last
* previous extents.
* <li><i>offset</i>: position in pixels of the available rectangular area,
* where start drawing the map.
* <li><i>dist1pixel</i>: the distance in <i>world coordinates</i> equivalent to
* 1 pixel in the view with the current extent.
* <li><i>dist3pixel</i>: the distance in <i>world coordinates</i> equivalent to
* 3 pixels in the view with the current extent.
* <li><i>listeners</i>: list with the {@link ViewPortListener ViewPortListener}
* registered.
* </ul>
* </p>
*
* @author Vicente Caballero Navarro
*/
public class ViewPort {
/**
* <p>
* Area selected by user using some tool.
* </p>
*
* <p>
* When the zoom changes (for instance when using the zoom in or zoom out
* tools, but also zooming to a selected feature or shape) the extent that
* covers that area is the value returned by this method. It is not the
* actual area shown in the view because it does not care about the aspect
* ratio of the available area. However, any part of the real world
* contained in this extent is shown in the view.
* </p>
* <p>
* Probably this is not what you are looking for. If you are looking for the
* complete extent currently shown, you must use
* {@linkplain #getAdjustedExtent()} method which returns the extent that
* contains this one but regarding the current view's aspect ratio.
* </p>
*
* @see #getExtent()
* @see #setExtent(Rectangle2D)
*/
protected Rectangle2D extent;
/**
* <p>
* Location and dimensions of the extent adjusted to the image size.
* </p>
*
* @see #getAdjustedExtent()
*/
protected Rectangle2D adjustedExtent;
/**
* Draw version of the context. It's used for know when de componend has
* changed any visualization property
*
* @see getDrawVersion
* @see updateDrawVersion
*/
private long drawVersion = 0L;
/**
* <p>
* History with the last extents of the view.
* </p>
*
* @see #setPreviousExtent()
* @see #getExtents()
*/
protected ExtentHistory extents = new ExtentHistory();
/**
* <p>
* Size in <i>screen coordinates</i> of the rectangle where the image is
* displayed.
* </p>
* <p>
* Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
*
* <ul>
* <li>The new {@link #scale scale} .
* <li>The new {@link #adjustedExtent adjustableExtent} .
* <li>The new {@link #trans trans} .
* <li>The new real world coordinates equivalent to 1 pixel (
* {@link #dist1pixel dist1pixel}) .
* <li>The new real world coordinates equivalent to 3 pixels (
* {@link #dist3pixel dist3pixel}) .
* </ul>
* </p>
*
* @see #getImageSize()
* @see #getImageHeight()
* @see #getImageWidth()
* @see #setImageSize(Dimension)
*/
private Dimension imageSize;
/**
* <p>
* the affine transformation between the {@link #extent extent} in <i>map 2D
* coordinates</i> to the image area in the screen, in <i>screen 2D
* coordinates</i> (pixels).
* </p>
*
* @see AffineTransform
*
* @see #getAffineTransform()
* @see #setAffineTransform(AffineTransform)
* @see #calculateAffineTransform()
*/
private AffineTransform trans = new AffineTransform();
/**
* <p>
* Measurement unit used for measuring distances and displaying information.
* </p>
*
* @see #getDistanceUnits()
* @see #setDistanceUnits(int)
*/
private Unit distanceUnits = Unit.METERS;
/**
* <p>
* Measurement unit used for measuring areas and displaying information.
* </p>
*
* @see #getDistanceArea()
* @see #setDistanceArea(int)
*/
private Unit distanceArea = Unit.METERS;
/**
* <p>
* Measurement unit used by this view port for the map.
* </p>
*
* @see #getMapUnits()
* @see #setMapUnits(int)
*/
private Unit mapUnits = Unit.METERS;
/**
* <p>
* The offset is the position where start drawing the map.
* </p>
* <p>
* The offset of a <a href="http://www.gvsig.gva.es/">gvSIG</a>'s
* <i>View</i> is always (0, 0) because the drawing area fits with the full
* window area. But in a <a href="http://www.gvsig.gva.es/">gvSIG</a>'s
* <i>Layout</i> it's up to the place where the <code>FFrameView</code> is
* located.
* </p>
*
* @see #getOffset()
* @see #setOffset(Point2D)
*/
private Point2D offset = new Point2D.Double(0, 0);
/**
* <p>
* Clipping area.
* </p>
*/
private Rectangle2D clip;
/**
* <p>
* Represents the distance in <i>world coordinates</i> equivalent to 1 pixel
* in the view with the current extent.
* </p>
*
* @see #getDist1pixel()
* @see #setDist1pixel(double)
*/
private double dist1pixel;
/**
* <p>
* Represents the distance in <i>world coordinates</i> equivalent to 3
* pixels in the view with the current extent.
* </p>
*
* @see #getDist3pixel()
* @see #setDist3pixel(double)
*/
private double dist3pixel;
/**
* <p>
* Ratio between the size of <code>imageSize</code> and <code>extent</code>:
* <br>
* <i>
*
* <pre>
* min{(imageSize.getHeight()/extent.getHeight(), imageSize.getWidth()/extent.getWidth())}
* </pre>
*
* </i>
* </p>
*/
private double scale;
/**
* <p>
* Clipping area.
* </p>
*
* @see #setClipRect(Rectangle2D)
*/
private Rectangle2D cliprect;
/**
* <p>
* Enables or disables the <i>"adjustable extent"</i> mode.
* </p>
*
* <p>
* When calculates the affine transform, if
* <ul>
* <li><i>enabled</i>: the new <code>adjustedExtent</code> will have the (X,
* Y) coordinates of the <code>extent</code> and an area that will be an
* scale of the image size. That area will have different height or width
* (not both) of the extent according the least ratio (height or width) in
*
* <pre>
* image.size/extent.size"
* </pre>.
* <li><i>disabled</i>: the new <code>adjustedExtent</code> will be like
* <code>extent</code>.
* </ul>
* </p>
*
* @see #setAdjustable(boolean)
*/
private boolean adjustableExtent = true;
private EventBus eventBus;
private MapContext mapContext;
/**
* <p>
* Creates a new view port with the information of the projection in
* <code>proj</code> argument, and default configuration:
* </p>
* <p>
* <ul>
* <li><i><code>distanceUnits</code></i> = meters
* <li><i><code>mapUnits</code></i> = meters
* <li><i><code>backColor</code></i> = <i>undefined</i>
* <li><i><code>offset</code></i> = <code>new Point2D.Double(0, 0);</code>
* </ul>
* </p>
*
* @param proj
* information of the projection for this view port
*/
public ViewPort(EventBus eventBus, MapContext mapContext) {
// Por defecto
this.eventBus = eventBus;
this.mapContext = mapContext;
}
/**
* <p>
* Changes the status of the <i>"adjustable extent"</i> option to enabled or
* disabled.
* </p>
*
* <p>
* If view port isn't adjustable, won't bear in mind the aspect ratio of the
* available rectangular area to calculate the affine transform from the
* original map in real coordinates. (Won't scale the image to adapt it to
* the available rectangular area).
* </p>
*
* @param boolean the boolean to be set
*/
public void setAdjustable(boolean adjustable) {
if (adjustable == adjustableExtent) {
return;
}
adjustableExtent = adjustable;
this.updateDrawVersion();
}
/**
* <p>
* Converts and returns the distance <code>d</code>, that is in <i>map
* coordinates</i> to <i>screen coordinates</i> using a <i>delta
* transform</i> with the transformation affine information in the
* {@link #trans #trans} attribute.
* </p>
*
* @param d
* distance in <i>map coordinates</i>
*
* @return distance equivalent in <i>screen coordinates</i>
*
* @see #toMapDistance(int)
* @see AffineTransform#deltaTransform(Point2D, Point2D)S
*/
public int fromMapDistance(double d) {
Point2D.Double pWorld = new Point2D.Double(1, 1);
Point2D.Double pScreen = new Point2D.Double();
try {
trans.deltaTransform(pWorld, pScreen);
} catch (Exception e) {
System.err.print(e.getMessage());
}
return (int) Math.round(d * pScreen.x);
}
/**
* <p>
* Converts and returns the 2D point <code>(x,y)</code>, that is in <i>map
* coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
* transformation in the {@link #trans #trans} attribute.
* </p>
*
* @param x
* the <code>x</code> <i>map coordinate</i> of a 2D point
* @param y
* the <code>y</code> <i>map coordinate</i> of a 2D point
*
* @return 2D point equivalent in <i>screen coordinates</i> (pixels)
*
* @see #fromMapPoint(Point2D)
* @see AffineTransform#transform(Point2D, Point2D)
*/
public Point2D fromMapPoint(double x, double y) {
Point2D.Double pWorld = new Point2D.Double(x, y);
Point2D.Double pScreen = new Point2D.Double();
try {
trans.transform(pWorld, pScreen);
} catch (Exception e) {
System.err.print(e.getMessage());
}
return pScreen;
}
/**
* <p>
* Converts and returns the 2D point argument, that is in <i>map
* coordinates</i> to <i>screen coordinates</i> (pixels) using the affine
* transformation in the {@link #trans #trans} attribute.
* </p>
*
* @param point
* the 2D point in <i>map coordinates</i>
*
* @return 2D point equivalent in <i>screen coordinates</i> (pixels)
*
* @see #toMapPoint(Point2D)
* @see #fromMapPoint(double, double)
*/
public Point2D fromMapPoint(Point2D point) {
return fromMapPoint(point.getX(), point.getY());
}
/**
* <p>
* Converts and returns the 2D point <code>(x,y)</code>, that is in
* <i>screen coordinates</i> (pixels) to <i>map coordinates</i> using the
* affine transformation in the {@link #trans #trans} attribute.
* </p>
*
* @param x
* the <code>x</code> <i>screen coordinate</i> of a 2D point
* @param y
* the <code>y</code> <i>screen coordinate</i> of a 2D point
*
* @return 2D point equivalent in <i>map coordinates</i>
*
* @see #toMapPoint(Point2D)
* @see #fromMapPoint(double, double)
*/
public Point2D toMapPoint(int x, int y) {
Point pScreen = new Point(x, y);
return toMapPoint(pScreen);
}
/**
* <p>
* Converts and returns the {@link Rectangle2D Rectangle2D}, that is in
* <i>screen coordinates</i> (pixels) to <i>map coordinates</i> using
* {@linkplain #toMapDistance(int)}, and {@linkplain #toMapPoint(int, int)}.
* </p>
*
* @param r
* the 2D rectangle in <i>screen coordinates</i> (pixels)
* @return 2D rectangle equivalent in <i>map coordinates</i>
*
* @see #fromMapRectangle(Rectangle2D)
* @see #toMapDistance(int)
* @see #toMapPoint(int, int)
*/
public Rectangle2D toMapRectangle(Rectangle2D r) {
Rectangle2D rect = new Rectangle2D.Double();
Point2D p1 = toMapPoint((int) r.getX(), (int) r.getY());
Point2D p2 = toMapPoint((int) r.getMaxX(), (int) r.getMaxY());
rect.setFrameFromDiagonal(p1, p2);
return rect;
}
/**
* <p>
* Converts and returns the distance <code>d</code>, that is in <i>screen
* coordinates</i> to <i>map coordinates</i> using the transformation affine
* information in the {@link #trans #trans} attribute.
* </p>
*
* @param d
* distance in pixels
*
* @return distance equivalent in <i>map coordinates</i>
*
* @see #fromMapDistance(double)
* @see AffineTransform
*/
public double toMapDistance(int d) {
double dist = d / trans.getScaleX();
return dist;
}
/**
* <p>
* Converts and returns the 2D point argument, that is in <i>screen
* coordinates</i> (pixels) to <i>map coordinates</i> using the inverse
* affine transformation of the {@link #trans #trans} attribute.
* </p>
*
* @param pScreen
* the 2D point in <i>screen coordinates</i> (pixels)
*
* @return 2D point equivalent in <i>map coordinates</i>
*
* @see #toMapPoint(int, int)
* @see AffineTransform#createInverse()
* @see AffineTransform#transform(Point2D, Point2D)
*/
public Point2D toMapPoint(Point2D pScreen) {
Point2D.Double pWorld = new Point2D.Double();
AffineTransform at;
try {
at = trans.createInverse();
at.transform(pScreen, pWorld);
} catch (NoninvertibleTransformException e) {
throw new RuntimeException("Non invertible transform Exception", e);
}
return pWorld;
}
/**
* <p>
* Returns the real distance (in <i>world coordinates</i>) at the graphic
* layers of two 2D points (in <i>map coordinates</i>) of the plane where is
* selected the <i>extent</i>.
* </p>
* <p>
* If the projection of this view is UTM, considers the Earth curvature.
* </p>
*
* @param pt1
* a 2D point in <i>map coordinates</i>
* @param pt2
* another 2D point in <i>map coordinates</i>
*
* @return the distance in meters between the two points 2D
* @throws TransformException
* if the distance cannot be computed
*/
public double distanceWorld(Point2D pt1, Point2D pt2)
throws TransformException {
CoordinateReferenceSystem crs = mapContext.getCRS();
if (crs != null && !(crs instanceof ProjectedCRS)) {
GeodeticCalculator calculator = new GeodeticCalculator(crs);
calculator.setStartingGeographicPoint(pt1);
calculator.setDestinationGeographicPoint(pt2);
return calculator.getOrthodromicDistance();
} else {
return (pt1.distance(pt2) * getMapUnits().toMeter());
}
}
/**
* <p>
* Sets as extent and adjusted extent of this view port, the previous.
* Recalculating its parameters.
* </p>
*
* @see #getExtents()
* @see #calculateAffineTransform()
*/
public void setPreviousExtent() {
this.updateDrawVersion();
extent = extents.removePrev();
// Calcula la transformaci�n af�n
calculateAffineTransform();
// Lanzamos los eventos de extent cambiado
callExtentChanged(getAdjustedExtent());
}
/**
* <p>
* Gets the area selected by user using some tool.
* </p>
*
* <p>
* When the zoom changes (for instance using the <i>zoom in</i> or <i>zoom
* out</i> tools, but also zooming to a selected feature or shape) the
* extent that covers that area is the value returned by this method. It is
* not the actual area shown because it doesn't care about the aspect ratio
* of the image size of the view. However, any part of the real world
* contained in this extent is shown in the view.
* </p>
*
* <p>
* If you are looking for the complete extent currently shown, you must use
* the {@linkplain #getAdjustedExtent()} method.
* </p>
*
* @return the current extent
*
* @see #setExtent(Rectangle2D)
* @see #getAdjustedExtent()
* @see #setPreviousExtent()
* @see #getExtents()
*/
public Rectangle2D getExtent() {
return extent;
}
/**
* <p>
* Changes the <i>extent</i> and <i>adjusted extent</i> of this view port:<br>
* <ul>
* <li>Stores the previous extent.
* <li>Calculates the new extent using <code>r</code>:
*
* <pre>
* extent = new Rectangle2D.Double(r.getMinX() - 0.1, r.getMinY() - 0.1,
* r.getWidth() + 0.2, r.getHeight() + 0.2);
* </pre>
*
* <li>Executes {@linkplain #calculateAffineTransform()}: getting the new
* scale, adjusted extent, affine transformation between map and screen
* coordinates, the real world coordinates equivalent to 1 pixel, and the
* real world coordinates equivalent to 3 pixels.
* <li>Notifies all {@link ViewPortListener ViewPortListener} registered
* that the extent has changed.
* </ul>
* </p>
*
* @param r
* the new extent
*
* @see #getExtent()
* @see #getExtents()
* @see #calculateAffineTransform()
* @see #setPreviousExtent()
*/
public void setExtent(Rectangle2D r) {
Rectangle2D newExtent = null;
// Esto comprueba que el extent no es de anchura o altura = "0"
// y si es as� lo redimensiona.
if (r != null && ((r.getWidth() == 0) || (r.getHeight() == 0))) {
newExtent = new Rectangle2D.Double(r.getMinX() - 0.1,
r.getMinY() - 0.1, r.getWidth() + 0.2, r.getHeight() + 0.2);
} else {
newExtent = r;
}
if (this.extent != null && this.extent.equals(newExtent)) {
return;
}
if (extent != null) {
extents.put(extent);
}
this.updateDrawVersion();
this.extent = newExtent;
// Calcula la transformaci�n af�n
calculateAffineTransform();
// Lanzamos los eventos de extent cambiado
callExtentChanged(getAdjustedExtent());
}
/**
* <p>
* Changes the <i>extent</i> and <i>adjusted extent</i> of this view port:<br>
* <ul>
* <li>Executes {@linkplain #calculateAffineTransform()}: getting the new
* scale, adjusted extent, affine transformation between map and screen
* coordinates, the real world coordinates equivalent to 1 pixel, and the
* real world coordinates equivalent to 3 pixels.
* <li>Notifies to all {@link ViewPortListener ViewPortListener} registered
* that the extent has changed.
* </ul>
* </p>
*
* @see #setExtent(Rectangle2D)
* @see #calculateAffineTransform()
*/
public void refreshExtent() {
// this.scale = scale;
// Calcula la transformaci�n af�n
calculateAffineTransform();
// Lanzamos los eventos de extent cambiado
callExtentChanged(getAdjustedExtent());
}
/**
* <p>
* Affine transformation between <i>map 2D coordinates</i> to <i>screen 2D
* coordinates</i> (pixels), preserving the "straightness" and "parallelism"
* of the lines.
* </p>
*
* @return the affine transformation
*
* @see #setAffineTransform(AffineTransform)
* @see #calculateAffineTransform()
*/
public AffineTransform getAffineTransform() {
return trans;
}
/**
* <p>
* Returns the size of the image projected.
* </p>
*
* @return the image size
*
* @see #setImageSize(Dimension)
* @see #getImageHeight()
* @see #getImageWidth()
*/
public Dimension getImageSize() {
return imageSize;
}
/**
* <p>
* Sets the size of the image projected, recalculating the parameters of
* this view port.
* </p>
*
* @param imageSize
* the image size
*
* @see #getImageSize()
* @see #calculateAffineTransform()
*/
public void setImageSize(Dimension imageSize) {
if (this.imageSize == null || (!this.imageSize.equals(imageSize))) {
this.updateDrawVersion();
this.imageSize = imageSize;
calculateAffineTransform();
}
}
/**
* <p>
* Notifies to all view port listeners registered, that the adjusted extent
* of this view port has changed.
* </p>
*
* @param newRect
* the new adjusted extend
*
* @see #refreshExtent()
* @see #setExtent(Rectangle2D)
* @see #setPreviousExtent()
* @see ExtentEvent
* @see ViewPortListener
*/
protected void callExtentChanged(Rectangle2D newRect) {
eventBus.fireEvent(new ExtentChangeEvent(mapContext));
}
/**
* <p>
* Calculates the affine transformation between the {@link #extent extent}
* in <i>map 2D coordinates</i> to the image area in the screen, in
* <i>screen 2D coordinates</i> (pixels).
* </p>
*
* <p>
* This process recalculates some parameters of this view port:<br>
*
* <ul>
* <li>The new {@link #scale scale} .
* <li>The new {@link #adjustedExtent adjustedExtent} .
* <li>The new {@link #trans trans} .
* <li>The new real world coordinates equivalent to 1 pixel (
* {@link #dist1pixel dist1pixel}) .
* <li>The new real world coordinates equivalent to 3 pixels (
* {@link #dist3pixel dist3pixel}) .
* </ul>
* </p>
*
* @see #getAffineTransform()
* @see #setAffineTransform(AffineTransform)
* @see #refreshExtent()
* @see #setExtent(Rectangle2D)
* @see #setImageSize(Dimension)
* @see #setPreviousExtent()
* @see #createFromXML(XMLEntity)
* @see AffineTransform
*/
private void calculateAffineTransform() {
if ((imageSize == null) || (extent == null)
|| (imageSize.getWidth() <= 0) || (imageSize.getHeight() <= 0)) {
return;
}
AffineTransform escalado = new AffineTransform();
AffineTransform translacion = new AffineTransform();
double escalaX;
double escalaY;
escalaX = imageSize.getWidth() / extent.getWidth();
escalaY = imageSize.getHeight() / extent.getHeight();
double xCenter = extent.getCenterX();
double yCenter = extent.getCenterY();
double newHeight;
double newWidth;
adjustedExtent = new Rectangle2D.Double();
if (adjustableExtent) {
if (escalaX < escalaY) {
scale = escalaX;
newHeight = imageSize.getHeight() / scale;
adjustedExtent.setRect(xCenter - (extent.getWidth() / 2.0),
yCenter - (newHeight / 2.0), extent.getWidth(),
newHeight);
} else {
scale = escalaY;
newWidth = imageSize.getWidth() / scale;
adjustedExtent.setRect(xCenter - (newWidth / 2.0), yCenter
- (extent.getHeight() / 2.0), newWidth,
extent.getHeight());
}
escalado.setToScale(scale, -scale);
} else { // adjusted is same as extent
scale = escalaX;
adjustedExtent.setFrame(extent);
escalado.setToScale(escalaX, -escalaY);
}
translacion.setToTranslation(-getAdjustedExtent().getX(),
-getAdjustedExtent().getY() - getAdjustedExtent().getHeight());
AffineTransform offsetTrans = new AffineTransform();
offsetTrans.setToTranslation(offset.getX(), offset.getY());
trans.setToIdentity();
trans.concatenate(offsetTrans);
trans.concatenate(escalado);
trans.concatenate(translacion);
// Calculamos las distancias de 1 pixel y 3 pixel con esa
// transformaci�n
// de coordenadas, de forma que est�n precalculadas para cuando las
// necesitemos
AffineTransform at;
try {
at = trans.createInverse();
java.awt.Point pPixel = new java.awt.Point(1, 1);
Point2D.Float pProv = new Point2D.Float();
at.deltaTransform(pPixel, pProv);
dist1pixel = pProv.x;
dist3pixel = 3 * pProv.x;
} catch (NoninvertibleTransformException e) {
System.err.println("transformada afin = " + trans.toString());
System.err.println("extent = " + extent.toString() + " imageSize= "
+ imageSize.toString());
throw new RuntimeException("Non invertible transform Exception", e);
}
}
/**
* <p>
* Sets the offset.
* </p>
* <p>
* The offset is the position where start drawing the map.
* </p>
*
* @param p
* 2D point that represents the offset in pixels
*
* @see #getOffset()
*/
public void setOffset(Point2D p) {
if (!offset.equals(p)) {
this.updateDrawVersion();
offset = p;
}
}
/**
* <p>
* Gets the offset.
* </p>
* <p>
* The offset is the position where start drawing the map.
* </p>
*
* @return 2D point that represents the offset in pixels
*
* @see #setOffset(Point2D)
*/
public Point2D getOffset() {
return offset;
}
/**
* <p>
* Returns the extent currently covered by the view adjusted (scaled) to the
* image size aspect.
* </p>
*
* @return extent of the view adjusted to the image size aspect
*
* @see #setAdjustable(boolean)
*/
public Rectangle2D getAdjustedExtent() {
if (cliprect != null) {
return adjustedExtent.createIntersection(cliprect);
}
return adjustedExtent;
}
/**
* <p>
* Returns the measurement unit of this view port used for measuring
* distances and displaying information.
* </p>
*
* @return the measurement unit of this view used for measuring distances
* and displaying information
*
* @see #setDistanceUnits(int)
*/
public Unit getDistanceUnits() {
return distanceUnits;
}
/**
* <p>
* Returns the measurement unit of this view port used for measuring areas
* and displaying information.
* </p>
*
* @return the measurement unit of this view used for measuring areas and
* displaying information
*
* @see #setDistanceUnits(int)
*/
public Unit getDistanceArea() {
return distanceArea;
}
/**
* <p>
* Sets the measurement unit of this view port used for measuring distances
* and displaying information.
* </p>
*
* @param distanceUnits
* the measurement unit of this view used for measuring distances
* and displaying information
*
* @see #getDistanceUnits()
*/
public void setDistanceUnits(Unit distanceUnits) {
this.distanceUnits = distanceUnits;
}
/**
* <p>
* Sets the measurement unit of this view port used for measuring areas and
* displaying information.
* </p>
*
* @param distanceUnits
* the measurement unit of this view used for measuring areas and
* displaying information
*
* @see #getDistanceUnits()
*/
public void setDistanceArea(Unit distanceArea) {
this.distanceArea = distanceArea;
}
/**
* <p>
* Gets the measurement unit used by this view port for the map.
* </p>
*
* @return Returns the current map measure unit
*
* @see #setMapUnits(int)
*/
public Unit getMapUnits() {
return mapUnits;
}
/**
* <p>
* Sets the measurement unit used by this view port for the map.
* </p>
*
* @param mapUnits
* the new map measure unit
*
* @see #getMapUnits()
*/
public void setMapUnits(Unit mapUnits) {
this.mapUnits = mapUnits;
}
/**
* <p>
* Gets the width in <i>screen coordinates</i> of the rectangle where the
* image is displayed.
* </p>
* <p>
* Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
*
* <ul>
* <li>The new {@link #scale scale} .
* <li>The new {@link #adjustedExtent adjustableExtent} .
* <li>The new {@link #trans trans} .
* <li>The new real world coordinates equivalent to 1 pixel (
* {@link #dist1pixel dist1pixel}) .
* <li>The new real world coordinates equivalent to 3 pixels (
* {@link #dist3pixel dist3pixel}) .
* </ul>
* </p>
*
* @see #getImageHeight()
* @see #getImageSize()
* @see #setImageSize(Dimension)
*/
public int getImageWidth() {
return imageSize.width;
}
/**
* <p>
* Gets the height in <i>screen coordinates</i> of the rectangle where the
* image is displayed.
* </p>
* <p>
* Used by {@linkplain #calculateAffineTransform()} to calculate:<br>
*
* <ul>
* <li>The new {@link #scale scale} .
* <li>The new {@link #adjustedExtent adjustableExtent} .
* <li>The new {@link #trans trans} .
* <li>The new real world coordinates equivalent to 1 pixel (
* {@link #dist1pixel dist1pixel}) .
* <li>The new real world coordinates equivalent to 3 pixels (
* {@link #dist3pixel dist3pixel}) .
* </ul>
* </p>
*
* @see #getImageWidth()
* @see #getImageSize()
* @see #setImageSize(Dimension)
*/
public int getImageHeight() {
return imageSize.height;
}
/**
* <p>
* Gets the distance in <i>world coordinates</i> equivalent to 1 pixel in
* the view with the current extent.
* </p>
*
* @return the distance
*
* @see #setDist1pixel(double)
*/
public double getDist1pixel() {
return dist1pixel;
}
/**
* <p>
* Sets the distance in <i>world coordinates</i> equivalent to 1 pixel in
* the view with the current extent.
* </p>
*
* @param dist1pixel
* the distance
*
* @see #getDist1pixel()
*/
public void setDist1pixel(double dist1pixel) {
if (dist1pixel == this.dist1pixel) {
return;
}
this.updateDrawVersion();
this.dist1pixel = dist1pixel;
}
/**
* <p>
* Gets the distance in <i>world coordinates</i> equivalent to 3 pixels in
* the view with the current extent.
* </p>
*
* @return the distance
*
* @see #setDist3pixel(double)
*/
public double getDist3pixel() {
return dist3pixel;
}
/**
* <p>
* Sets the distance in <i>world coordinates</i> equivalent to 3 pixels in
* the view with the current extent.
* </p>
*
* @param dist3pixel
* the distance
*
* @see #getDist3pixel()
*/
public void setDist3pixel(double dist3pixel) {
if (this.dist3pixel == dist3pixel) {
return;
}
this.updateDrawVersion();
this.dist3pixel = dist3pixel;
}
/**
* <p>
* Returns the last previous extents of this view port.
* </p>
*
* @return the last previous extents of this view port
*
* @see #setPreviousExtent()
*/
public ExtentHistory getExtents() {
return extents;
}
/**
* <p>
* Returns a <code>String</code> representation of the main values of this
* view port: <code>{@linkplain #extent}</code>,
* <code>{@linkplain #adjustedExtent}</code>,
* <code>{@linkplain #imageSize}</code>, <code>{@linkplain #scale}</code>,
* and <code>{@linkplain #trans}</code>.
* </p>
*
* @return a <code>string</code> representation of the main values of this
* view port
*/
public String toString() {
String str;
str = "Datos del viewPort:\nExtent=" + extent + "\nadjustedExtent="
+ adjustedExtent + "\nimageSize=" + imageSize + "\nescale="
+ scale + "\ntrans=" + trans;
return str;
}
/**
* <p>
* Sets the position and size of the clipping rectangle.
* </p>
*
* @param rectView
* the clipping rectangle to set
*/
public void setClipRect(Rectangle2D rectView) {
this.updateDrawVersion();
cliprect = rectView;
}
/**
* <p>
* Converts and returns the {@link Rectangle2D Rectangle2D}, that is in
* <i>map coordinates</i> to <i>screen coordinates</i> (pixels) using an
* <i>inverse transform</i> with the transformation affine information in
* the {@link #trans #trans} attribute.
* </p>
*
* @param r
* the 2D rectangle in <i>map coordinates</i>
* @return 2D rectangle equivalent in <i>screen coordinates</i> (pixels)
*
* @see #toMapRectangle(Rectangle2D)
* @see #fromMapDistance(double)
* @see #fromMapPoint(Point2D)
*/
public Rectangle2D fromMapRectangle(Rectangle2D r) {
Rectangle2D rect = new Rectangle2D.Double();
Point2D p1 = fromMapPoint((int) r.getX(), (int) r.getY());
Point2D p2 = fromMapPoint((int) r.getMaxX(), (int) r.getMaxY());
rect.setFrameFromDiagonal(p1, p2);
return rect;
}
/**
* <p>
* Returns the scale of the view in the screen.
* </p>
*
* @return one of this values:
* <ul>
* <li>the scale of the adjusted extent scale of the view in the
* screen
* <li><code>-1</code> if there is no image
* <li><code>0</code> if there is no extent defined for the image
* </ul>
* @throws FactoryException
* if the scale cannot be obtained
* @throws TransformException
* if the scale cannot be obtained
*
* @see #setScaleView(long)
* @see ViewPort#getAdjustedExtent()
* @see IProjection#getScale(double, double, double, double)
*/
public double getScaleView() throws TransformException, FactoryException {
Dimension size = getImageSize();
if (size == null) {
return -1;
}
Rectangle2D ext = getAdjustedExtent();
if (ext == null) {
return 0;
}
double dpi = getScreenDPI();
CoordinateReferenceSystem crs = mapContext.getCRS();
if (crs == null) {
double widthCm = ((getImageSize().getWidth() / dpi) * 2.54);
return (long) (getAdjustedExtent().getWidth() / widthCm * getMapUnits()
.toMeter());
} else {
ReferencedEnvelope envelope = new ReferencedEnvelope(ext, crs);
return RendererUtilities.calculateScale(envelope, size.width,
size.height, dpi);
}
}
/**
* <p>
* Returns the screen resolution (Dots Per Inch) as it was defined by the
* user's preference, or by default as it is defined in the default Toolkit.
* </p>
*
* @return double with the screen's dpi
*/
public static double getScreenDPI() {
Preferences prefsResolution = Preferences.userRoot().node(
"gvsig.configuration.screen");
Toolkit kit = Toolkit.getDefaultToolkit();
double dpi = prefsResolution.getInt("dpi", kit.getScreenResolution());
return dpi;
}
public long getDrawVersion() {
return this.drawVersion;
}
protected void updateDrawVersion() {
this.drawVersion++;
}
private double zoomFactor = 1d;
public double getZoomFactor() {
return zoomFactor;
}
public void setZoomFactor(double z) {
this.zoomFactor = z;
}
public void setScale(long scale) {
double dpi = getScreenDPI();
if (getImageSize() == null)
return;
CoordinateReferenceSystem crs = mapContext.getCRS();
if (getAdjustedExtent() == null) {
return;
}
assert false : "Uncomment following line";
// setExtent(ProjectionUtils.getExtent(crs, getAdjustedExtent(), //
// extent
// scale, getImageWidth(), getImageHeight(), 100 * getMapUnits()
// .toMeter(), getDistanceUnits().toMeter(), dpi));
}
}