/*
* GeoTools - The Open Source Java GIS Tookit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This file is hereby placed into the Public Domain. This means anyone is
* free to do whatever they wish with this file. Use it well and enjoy!
*/
package org.geotools.demo.swing;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.Timer;
import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.geotools.swing.JMapPane;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Simple example of an animated object (known as a 'sprite')
* moving over a map. The object is a flying saucer (actually
* the GeoTools logo) moving over a map of country boundaries.
*
* @author Michael Bedward
*/
public class FlyingSaucer extends JMapPane {
private static final Random rand = new Random();
// Load the GeoTools logo image which will be our flying saucer
private static final Image SPRITE_IMAGE;
static {
SPRITE_IMAGE = new ImageIcon(FlyingSaucer.class.getResource("/images/compass_100.png")).getImage();
}
// Arbitrary distance to move at each step of the animation
// in world units.
private double movementDistance = 3.0;
// X and Y direction of the flying saucer where a value of
// 1 indicates increasing ordinate and -1 is decreasing
private int xdir = 1;
private int ydir = 1;
// The rectangle (in world coordinates) that defines the flying
// saucer's current position
private ReferencedEnvelope spriteEnv;
private Raster spriteBackground;
private boolean firstDisplay = true;
// This animation will be driven by a timer which fires
// every 200 milliseconds. Each time it fires the drawSprite
// method is called to update the animation
private Timer animationTimer = new Timer(200, new ActionListener() {
public void actionPerformed(ActionEvent e) {
drawSprite();
}
});
// We override the JMapPane paintComponent method so that when
// the map needs to be redrawn (e.g. after the frame has been
// resized) the animation is stopped until rendering is complete.
@Override
protected void paintComponent(Graphics g) {
animationTimer.stop();
super.paintComponent(g);
}
// We override the JMapPane onRenderingCompleted method to
// restart the animation after the map has been drawn.
@Override
public void onRenderingCompleted() {
super.onRenderingCompleted();
spriteBackground = null;
animationTimer.start();
}
// This is the top-level animation method. It erases
// the sprite (if showing), updates its position and then
// draws it.
private void drawSprite() {
if (firstDisplay) {
setSpritePosition();
firstDisplay = false;
}
Graphics2D gr2D = (Graphics2D) getGraphics();
eraseSprite(gr2D);
moveSprite();
paintSprite(gr2D);
}
// Erase the sprite by replacing the background map section that
// was cached when the sprite was last drawn.
private void eraseSprite(Graphics2D gr2D) {
if (spriteBackground != null) {
Rectangle rect = spriteBackground.getBounds();
BufferedImage image = new BufferedImage(
rect.width, rect.height, BufferedImage.TYPE_INT_ARGB);
Raster child = spriteBackground.createChild(
rect.x, rect.y, rect.width, rect.height, 0, 0, null);
image.setData(child);
gr2D.setBackground(getBackground());
gr2D.clearRect(rect.x, rect.y, rect.width, rect.height);
gr2D.drawImage(image, rect.x, rect.y, null);
spriteBackground = null;
}
}
// Update the sprite's location. In this example we are simply
// moving at 45 degrees to the map edges and bouncing off when an
// edge is reached.
private void moveSprite() {
ReferencedEnvelope displayArea = getDisplayArea();
DirectPosition2D lower = new DirectPosition2D();
DirectPosition2D upper = new DirectPosition2D();
double xdelta = 0, ydelta = 0;
boolean done = false;
while (!done) {
lower.setLocation(spriteEnv.getLowerCorner());
upper.setLocation(spriteEnv.getUpperCorner());
xdelta = xdir * movementDistance;
ydelta = ydir * movementDistance;
lower.setLocation(lower.getX() + xdelta, lower.getY() + ydelta);
upper.setLocation(upper.getX() + xdelta, upper.getY() + ydelta);
boolean lowerIn = displayArea.contains(lower);
boolean upperIn = displayArea.contains(upper);
if (lowerIn && upperIn) {
done = true;
} else if (!lowerIn) {
if (lower.x < displayArea.getMinX()) {
xdir = -xdir;
} else if (lower.y < displayArea.getMinY()) {
ydir = -ydir;
}
} else if (!upperIn) {
if (upper.x > displayArea.getMaxX()) {
xdir = -xdir;
} else if (upper.y > displayArea.getMaxY()) {
ydir = -ydir;
}
}
}
spriteEnv.translate(xdelta, ydelta);
}
// Paint the sprite: before displaying the sprite image we
// cache that part of the background map image that will be
// covered by the sprite.
private void paintSprite(Graphics2D gr2D) {
Rectangle bounds = getSpriteScreenPos();
spriteBackground = getBaseImage().getData(bounds);
gr2D.drawImage(SPRITE_IMAGE, bounds.x, bounds.y, null);
}
// Set the sprite's intial position
private void setSpritePosition() {
ReferencedEnvelope worldBounds = null;
try {
worldBounds = getMapContext().getLayerBounds();
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
CoordinateReferenceSystem crs = worldBounds.getCoordinateReferenceSystem();
Rectangle screenBounds = getVisibleRect();
int w = SPRITE_IMAGE.getWidth(null);
int h = SPRITE_IMAGE.getHeight(null);
int x = screenBounds.x + rand.nextInt(screenBounds.width - w);
int y = screenBounds.y + rand.nextInt(screenBounds.height - h);
Rectangle r = new Rectangle(x, y, w, h);
AffineTransform tr = getScreenToWorldTransform();
Rectangle2D rworld = tr.createTransformedShape(r).getBounds2D();
spriteEnv = new ReferencedEnvelope(rworld, crs);
}
// Get the position of the sprite as screen coordinates
private Rectangle getSpriteScreenPos() {
AffineTransform tr = getWorldToScreenTransform();
Point2D lowerCorner = new Point2D.Double(spriteEnv.getMinX(), spriteEnv.getMinY());
Point2D upperCorner = new Point2D.Double(spriteEnv.getMaxX(), spriteEnv.getMaxY());
Point2D p0 = tr.transform(lowerCorner, null);
Point2D p1 = tr.transform(upperCorner, null);
Rectangle r = new Rectangle();
r.setFrameFromDiagonal(p0, p1);
return r;
}
// Main application method
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Animation example");
FlyingSaucer mapPane = new FlyingSaucer();
frame.getContentPane().add(mapPane);
frame.setSize(800, 500);
URL url = FlyingSaucer.class.getResource("/data/shapefiles/countries.shp");
FileDataStore store = FileDataStoreFinder.getDataStore(url);
FeatureSource featureSource = store.getFeatureSource();
// Create a map context and add our shapefile to it
MapContext map = new DefaultMapContext();
Style style = SLD.createPolygonStyle(Color.BLACK, Color.CYAN, 1.0F);
map.addLayer(featureSource, style);
mapPane.setMapContext(map);
mapPane.setRenderer(new StreamingRenderer());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}