package org.geotools.map;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.event.MapBoundsEvent;
import org.geotools.map.event.MapBoundsListener;
import org.geotools.map.event.MapBoundsEvent.Type;
import org.geotools.util.logging.Logging;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
/**
* Represents the area of a map to be displayed.
* <p>
* A viewport is used to stage information for map rendering; while the viewport provides support
* for bounds and coordinate reference system out of the box it is expected that the user data
* support is used to record additional information such as elevation and time as required for
* rendering.
*
* @author Jody
*
* @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/build/maven/javadoc/../../../modules/library/render/src/main/java/org/geotools/map/MapViewport.java $
*/
public class MapViewport {
/** The logger for the map module. */
static protected final Logger LOGGER = Logging.getLogger("org.geotools.map");
private Rectangle screenArea;
private ReferencedEnvelope bounds;
private CopyOnWriteArrayList<MapBoundsListener> boundsListeners;
/**
* Used by client application to track the bounds of this viewport.
*
* @param listener
*/
public void addMapBoundsListener(MapBoundsListener listener) {
if (boundsListeners == null) {
boundsListeners = new CopyOnWriteArrayList<MapBoundsListener>();
}
if (!boundsListeners.contains(listener)) {
boundsListeners.add(listener);
}
}
public void removeMapBoundsListener(MapBoundsListener listener) {
if (boundsListeners != null) {
boundsListeners.remove(listener);
}
}
/**
* The extent of the map to render (this is the main attribute of the map viewport).
* <p>
* Note Well: The bounds should match your screen aspect ratio (or the map will appear
* squashed). Please note this only covers spatial extent; you may wish to use the user data map
* to record the current viewport time or elevation.
*/
public ReferencedEnvelope getBounds() {
return bounds;
}
/**
* The coordinate reference system used for rendering the map.
* <p>
* The coordinate reference system used for rendering is often considered to be the "world"
* coordinate reference system; this is distinct from the coordinate reference system used for
* each layer (which is often data dependent).
* </p>
*
* @return coordinate reference system used for rendering the map.
*/
public CoordinateReferenceSystem getCoordianteReferenceSystem() {
return bounds == null ? null : bounds.getCoordinateReferenceSystem();
}
/**
* Set the <code>CoordinateReferenceSystem</code> for this map's internal viewport.
*
* @param crs
* @throws FactoryException
* @throws TransformException
*/
public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) {
if( bounds == null ){
bounds = new ReferencedEnvelope(crs);
}
else if (bounds.getCoordinateReferenceSystem() != crs) {
if (bounds != null) {
try {
ReferencedEnvelope old = bounds;
bounds = bounds.transform(crs, true);
fireMapBoundsListenerMapBoundsChanged(MapBoundsEvent.Type.CRS, old, bounds);
} catch (Exception e) {
LOGGER.log(Level.FINE, "Difficulty transforming to {0}", crs);
}
}
}
}
/**
* Notifies MapBoundsListeners about a change to the bounds or crs.
*
* @param event
* The event to be fired
*/
protected void fireMapBoundsListenerMapBoundsChanged(Type type, ReferencedEnvelope oldBounds,
ReferencedEnvelope newBounds) {
if (boundsListeners == null) {
return;
}
if (newBounds == bounds) {
// issue a copy to the boundsListeners for safety
newBounds = new ReferencedEnvelope(bounds);
}
MapBoundsEvent event = new MapBoundsEvent(this, type, oldBounds, newBounds);
for (MapBoundsListener boundsListener : boundsListeners) {
try {
boundsListener.mapBoundsChanged(event);
} catch (Throwable t) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.logp(Level.FINE, boundsListener.getClass().getName(),
"mapBoundsChanged", t.getLocalizedMessage(), t);
}
}
}
}
public void setBounds(ReferencedEnvelope bounds) {
ReferencedEnvelope old = this.bounds;
this.bounds = bounds;
fireMapBoundsListenerMapBoundsChanged(Type.BOUNDS, old, bounds);
}
/**
* Screen area to render into when drawing.
* @return screen area to render into when drawing.
*/
public Rectangle getScreenArea() {
return screenArea;
}
/**
* Screen area to render into when drawing.
* @param screenArea
*/
public void setScreenArea(Rectangle screenArea) {
// we could consider updating the bounds to have the correct aspect ratio
// matching this screen area?
this.screenArea = screenArea;
}
}