/* * <copyright> * Copyright 2013 BBN Technologies * </copyright> */ package com.bbn.openmap.layer.learn; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.util.Timer; import java.util.TimerTask; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.bbn.openmap.layer.OMGraphicHandlerLayer; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMPoint; import com.bbn.openmap.omGraphics.OMScalingIcon; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PaletteHelper; /** * A simple demonstration of doing animation on a Layer. This layer has a GUI * interface to add/remove points on the map, and they are moved randomly based * on timer intervals. All the parameters can be modified in the GUI. * * This layer uses code fetched from the AnimationTester and * AbstractGraphicLoader. * * @author dietrick */ public class SimpleAnimationLayer extends OMGraphicHandlerLayer { /** * Managed list of moving points, separate from the OMGraphicList the layer * will use to paint on the map. The objects on the lists are the same, but * the lists holding them are different to allow one list to be modified * without affecting the actions being performed on the other (concurrent * modification). */ OMGraphicList movingPoints = new OMGraphicList(); /** * A movement factor for the points. */ double movementFactor = 4.0; /** * Timer used to drive the animation for the layer. */ Timer timer; /** * The timer interval used to make the sprites move. */ int timerDelay = 3000; public SimpleAnimationLayer() { } /** * The prepare method always returns the OMGraphicList to be drawn on the * map. We're assuming that movingPoints holds the OMPoints as they are * modified. We need to create a separate OMGraphicList to return for * painting after we call generate() on the OMGraphics with the current map. * If we forget to call generate(), the OMGraphics will not know where they * are on the map, and they won't drawn themselves. * * @see com.bbn.openmap.layer.OMGraphicHandlerLayer#prepare() */ public synchronized OMGraphicList prepare() { OMGraphicList ret = new OMGraphicList(movingPoints); ret.generate(getProjection()); // The ret list is the one used for painting. return ret; } /** * Called when the layer is removed from the map. Cleanup! */ public void removed(Container cont) { movingPoints.clear(); stopTimer(); } /** * Create a new point for the list. We're going to look at the current * projection and find some random point within the height and width of the * map. If the projection isn't set on the layer yet (it hasn't been added * to the map), no point will be added. */ public void addMovingPoint() { Projection proj = getProjection(); if (proj == null) { return; } int numOfSpritesToAdd = 1; if (spinner != null) { try { numOfSpritesToAdd = Integer.parseInt(spinner.getValue().toString()); } catch (NumberFormatException nfe) { spinner.setValue(1); } } double mapHeight = proj.getHeight(); double mapWidth = proj.getWidth(); for (int i = 0; i < numOfSpritesToAdd; i++) { double ranY = (Math.random() * mapHeight); double ranX = (Math.random() * mapWidth); Point2D newLoc = proj.inverse(ranX, ranY); OMPoint point = new OMPoint(newLoc.getY(), newLoc.getX(), 5); point.setFillPaint(Color.red); movingPoints.add(point); } spriteCountLabel.setText(Integer.toString(movingPoints.size())); // Update the map doPrepare(); } /** * Clears the point list. */ public void clearMovingPoints() { movingPoints.clear(); spriteCountLabel.setText(Integer.toString(movingPoints.size())); // Update the map doPrepare(); stopTimer(); } /** * Called when the timer should be stopped. */ public void stopTimer() { if (timer != null) { timer.cancel(); timer.purge(); timer = null; } if (timerButton != null) { timerButton.setSelected(false); } } /** * Called when the timer should be started. * * @param interval the interval that the timer task gets executed on. */ public void resetTimer(int interval) { timerDelay = interval; stopTimer(); timer = new Timer(); timer.scheduleAtFixedRate(new ManageGraphicsTask(SimpleAnimationLayer.this), 0, timerDelay); if (timerButton != null) { timerButton.setSelected(true); } } JPanel panel = null; JButton addButton = null; JCheckBox timerButton = null; JSpinner spinner = null; JLabel spriteCountLabel = null; /** * This method is called by other OpenMap components to present a GUI * interface to a user. We keep an handle to a panel that we reuse for later * requests. */ public Component getGUI() { if (panel == null) { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); panel = new JPanel(gridbag); addButton = new JButton("Add Sprites"); addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { addMovingPoint(); } }); c.insets = new Insets(5, 5, 5, 2); c.anchor = GridBagConstraints.NORTHWEST; gridbag.setConstraints(addButton, c); panel.add(addButton); JButton clearButton = new JButton("Clear Sprites"); clearButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { clearMovingPoints(); } }); c.insets = new Insets(5, 2, 5, 5); c.anchor = GridBagConstraints.NORTHEAST; c.gridwidth = GridBagConstraints.REMAINDER; gridbag.setConstraints(clearButton, c); panel.add(clearButton); JLabel numSpritesLabel = new JLabel("Number of Sprites to Add:"); SpinnerModel numSpritesSpinnerModel = new SpinnerNumberModel(1, 1, 50, 1); spinner = new JSpinner(numSpritesSpinnerModel); numSpritesLabel.setLabelFor(spinner); c.insets = new Insets(0, 5, 5, 5); c.anchor = GridBagConstraints.WEST; c.gridwidth = GridBagConstraints.RELATIVE; gridbag.setConstraints(numSpritesLabel, c); panel.add(numSpritesLabel); c.anchor = GridBagConstraints.EAST; c.gridwidth = GridBagConstraints.REMAINDER; gridbag.setConstraints(spinner, c); panel.add(spinner); JLabel numMapSpritesLabel = new JLabel("Number of Sprites on Map:"); spriteCountLabel = new JLabel(Integer.toString(movingPoints.size())); c.insets = new Insets(0, 5, 5, 5); c.anchor = GridBagConstraints.WEST; c.gridwidth = GridBagConstraints.RELATIVE; gridbag.setConstraints(numMapSpritesLabel, c); panel.add(numMapSpritesLabel); c.anchor = GridBagConstraints.EAST; c.gridwidth = GridBagConstraints.REMAINDER; gridbag.setConstraints(spriteCountLabel, c); panel.add(spriteCountLabel); JSlider slider = new JSlider(JSlider.HORIZONTAL, 0/* min */, 20/* max */, 10/* initial */); java.util.Hashtable dict = new java.util.Hashtable(); dict.put(new Integer(5), new JLabel(".5")); dict.put(new Integer(10), new JLabel("1")); dict.put(new Integer(15), new JLabel("1.5")); dict.put(new Integer(20), new JLabel("2")); slider.setLabelTable(dict); slider.setPaintLabels(true); slider.setMajorTickSpacing(2); slider.setPaintTicks(true); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent ce) { JSlider slider2 = (JSlider) ce.getSource(); if (slider2.getValueIsAdjusting()) { float interval = ((float) (slider2.getValue()) + .01f) * 100f; Debug.output("SimpleAnimationLayer delay set to: " + interval / 1000f + " seconds"); resetTimer((int) interval); } } }); JPanel sliderPanel = PaletteHelper.createHorizontalPanel("Timer interval in seconds:"); sliderPanel.add(slider); c.insets = new Insets(0, 5, 5, 5); c.anchor = GridBagConstraints.WEST; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; gridbag.setConstraints(sliderPanel, c); panel.add(sliderPanel); timerButton = new JCheckBox("Run Timer", timer != null); timerButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { JCheckBox check = (JCheckBox) ae.getSource(); if (check.isSelected()) { resetTimer(timerDelay); } else { stopTimer(); } } }); gridbag.setConstraints(timerButton, c); panel.add(timerButton); } return panel; } /** * This is a TimerTask the timer uses to tell the points to update their * position. * * This is where you would do work to change the position of your OMGraphics * - whether by reading a data file, or checking a web site, whatever. This * method could also be the call another object could call with new * OMGraphics to display, if you wanted a different object to control the * updates. */ public class ManageGraphicsTask extends TimerTask { final SimpleAnimationLayer sal; public ManageGraphicsTask(SimpleAnimationLayer layer) { sal = layer; } /* * (non-Javadoc) * * @see java.util.TimerTask#run() */ @Override public void run() { Projection proj = sal.getProjection(); OMGraphicList safeList = new OMGraphicList(sal.movingPoints); for (OMGraphic point : safeList) { if (point instanceof OMPoint) { moveRandomly((OMPoint) point, sal.movementFactor, proj); } else if (point instanceof OMScalingIcon) { moveRandomly((OMScalingIcon) point, sal.movementFactor, proj); } if (proj != null) { point.generate(proj); } } /* * We have a couple of options here. We could call doPrepare(), * which will launch a separate thread to call prepare() and * repaint(). prepare() will call generate() on all the OMGraphics, * but we've already done that above. So we can ask for the event * thread to paint the layer by calling repaint(). This saves us * from launching a separate thread to do the same thing. If we * didn't call generate() on the points above, we would have to call * doPrepare() instead of repaint(), because the points need to be * generated after they are moved, in order to know where they get * drawn. */ sal.repaint(); } /** * Simple method to move an OMPoint around randomly. * * @param point the OMPoint to move. * @param factor a movement factor, in pixels. * @param proj the current map projection */ protected void moveRandomly(OMPoint point, double factor, Projection proj) { double hor = Math.random() - .5; double vert = Math.random() - .5; Point2D mapPoint = proj.forward(point.getLat(), point.getLon()); mapPoint.setLocation(mapPoint.getX() + (hor * factor), mapPoint.getY() + (vert * factor)); Point2D llp = proj.inverse(mapPoint); point.setLat(llp.getY()); point.setLon(llp.getX()); } /** * Simple method to move an OMPoint around randomly. * * @param point the OMPoint to move. * @param factor a movement factor, in pixels. * @param proj the current map projection */ protected void moveRandomly(OMScalingIcon point, double factor, Projection proj) { double hor = Math.random() - .5; double vert = Math.random() - .5; Point2D mapPoint = proj.forward(point.getLat(), point.getLon()); mapPoint.setLocation(mapPoint.getX() + (hor * factor), mapPoint.getY() + (vert * factor)); Point2D llp = proj.inverse(mapPoint); point.setLat(llp.getY()); point.setLon(llp.getX()); } } }