/* * Copyright (c) 2006, Swedish Institute of Computer Science. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: AreaViewer.java,v 1.3 2007/03/23 21:13:43 fros4943 Exp $ */ package se.sics.mrm; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.BufferedImage; import java.io.File; import java.net.URL; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.*; import javax.swing.*; import javax.swing.filechooser.FileFilter; import org.apache.log4j.Logger; import org.jdom.Element; import se.sics.cooja.*; import se.sics.cooja.interfaces.*; /** * The class AreaViewer belongs to the MRM package. * * It is used to visualize available radios, traffic between them as well * as the current radio propagation area of single radios. * Users may also add background images (such as maps) and color-analyze them * in order to add simulated obstacles in the radio medium. * * For more information about MRM see MRM.java * * @see MRM * @author Fredrik Osterlind */ @ClassDescription("MRM - Area Viewer") @PluginType(PluginType.SIM_PLUGIN) public class AreaViewer extends VisPlugin { private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(AreaViewer.class); private final JPanel canvas; private final VisPlugin thisPlugin; ChannelModel.TransmissionData dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH; ButtonGroup visTypeSelectionGroup; // General drawing parameters private Point lastHandledPosition = new Point(0,0); private double zoomCenterX = 0.0; private double zoomCenterY = 0.0; private Point zoomCenterPoint = new Point(); private double currentZoomX = 1.0f; private double currentZoomY = 1.0f; private double currentPanX = 0.0f; private double currentPanY = 0.0f; private boolean drawBackgroundImage = true; private boolean drawCalculatedObstacles = true; private boolean drawChannelProbabilities = true; private boolean drawRadios = true; private boolean drawRadioActivity = true; private boolean drawScaleArrow = true; // Background drawing parameters (meters) private double backgroundStartX = 0.0; private double backgroundStartY = 0.0; private double backgroundWidth = 0.0; private double backgroundHeight = 0.0; private Image backgroundImage = null; private File backgroundImageFile = null; // Obstacle drawing parameters (same scale as background) private boolean needToRepaintObstacleImage = false; private double obstacleStartX = 0.0; private double obstacleStartY = 0.0; private double obstacleWidth = 0.0; private double obstacleHeight = 0.0; private Image obstacleImage = null; // Channel probabilities drawing parameters (meters) private double channelStartX = 0.0; private double channelStartY = 0.0; private double channelWidth = 0.0; private double channelHeight = 0.0; private Image channelImage = null; private JSlider resolutionSlider; private JPanel controlPanel; private JScrollPane scrollControlPanel; private Simulation currentSimulation; private MRM currentRadioMedium; private ChannelModel currentChannelModel; private final String antennaImageFilename = "antenna.png"; private final Image antennaImage; private Radio selectedRadio = null; private boolean inSelectMode = true; private boolean inTrackMode = false; private Vector trackedComponents = null; // Coloring variables private JPanel coloringIntervalPanel = null; private double coloringHighest = 0; private double coloringLowest = 0; private boolean coloringIsFixed = true; private Thread attenuatorThread = null; private JCheckBox showSettingsBox; private JCheckBox backgroundCheckBox; private JCheckBox obstaclesCheckBox; private JCheckBox channelCheckBox; private JCheckBox radiosCheckBox; private JCheckBox radioActivityCheckBox; private JCheckBox arrowCheckBox; /** * Initializes an AreaViewer. * * @param simulationToVisualize Simulation using MRM */ public AreaViewer(Simulation simulationToVisualize, GUI gui) { super("MRM - Area Viewer", gui); currentSimulation = simulationToVisualize; currentRadioMedium = (MRM) currentSimulation.getRadioMedium(); currentChannelModel = currentRadioMedium.getChannelModel(); // We want to listen to changes both in the channel model as well as in the radio medium currentChannelModel.addSettingsObserver(channelModelSettingsObserver); currentRadioMedium.addSettingsObserver(radioMediumSettingsObserver); currentRadioMedium.addRadioMediumObserver(radioMediumActivityObserver); // Set initial size etc. setSize(500, 500); setVisible(true); thisPlugin = this; // Canvas mode radio buttons + show settings checkbox showSettingsBox = new JCheckBox ("settings", true); showSettingsBox.setAlignmentY(Component.TOP_ALIGNMENT); showSettingsBox.setContentAreaFilled(false); showSettingsBox.setActionCommand("toggle show settings"); showSettingsBox.addActionListener(canvasModeHandler); JRadioButton selectModeButton = new JRadioButton ("select"); selectModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); selectModeButton.setContentAreaFilled(false); selectModeButton.setActionCommand("set select mode"); selectModeButton.addActionListener(canvasModeHandler); selectModeButton.setSelected(true); JRadioButton panModeButton = new JRadioButton ("pan"); panModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); panModeButton.setContentAreaFilled(false); panModeButton.setActionCommand("set pan mode"); panModeButton.addActionListener(canvasModeHandler); JRadioButton zoomModeButton = new JRadioButton ("zoom"); zoomModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); zoomModeButton.setContentAreaFilled(false); zoomModeButton.setActionCommand("set zoom mode"); zoomModeButton.addActionListener(canvasModeHandler); JRadioButton trackModeButton = new JRadioButton ("track rays"); trackModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); trackModeButton.setContentAreaFilled(false); trackModeButton.setActionCommand("set track rays mode"); trackModeButton.addActionListener(canvasModeHandler); ButtonGroup group = new ButtonGroup(); group.add(selectModeButton); group.add(panModeButton); group.add(zoomModeButton); group.add(trackModeButton); // Create canvas canvas = new JPanel() { private static final long serialVersionUID = 1L; public void paintComponent(Graphics g) { super.paintComponent(g); repaintCanvas((Graphics2D) g); } }; canvas.setBorder(BorderFactory.createLineBorder(Color.BLACK)); canvas.setBackground(Color.WHITE); canvas.setLayout(new BorderLayout()); canvas.addMouseListener(canvasMouseHandler); // Create canvas mode panel JPanel canvasModePanel = new JPanel(); canvasModePanel.setOpaque(false); canvasModePanel.setLayout(new BoxLayout(canvasModePanel, BoxLayout.Y_AXIS)); canvasModePanel.setAlignmentY(Component.BOTTOM_ALIGNMENT); canvasModePanel.add(showSettingsBox); canvasModePanel.add(Box.createVerticalGlue()); canvasModePanel.add(selectModeButton); canvasModePanel.add(panModeButton); canvasModePanel.add(zoomModeButton); canvasModePanel.add(trackModeButton); canvas.add(BorderLayout.EAST, canvasModePanel); // Create control graphics panel JPanel graphicsComponentsPanel = new JPanel(); graphicsComponentsPanel.setLayout(new BoxLayout(graphicsComponentsPanel, BoxLayout.Y_AXIS)); graphicsComponentsPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); graphicsComponentsPanel.setAlignmentX(Component.CENTER_ALIGNMENT); graphicsComponentsPanel.add(new JLabel("Show components:")); backgroundCheckBox = new JCheckBox("Background", true); backgroundCheckBox.setActionCommand("toggle background"); backgroundCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(backgroundCheckBox); obstaclesCheckBox = new JCheckBox("Obstacles", true); obstaclesCheckBox.setActionCommand("toggle obstacles"); obstaclesCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(obstaclesCheckBox); channelCheckBox = new JCheckBox("Channel probability", true); channelCheckBox.setActionCommand("toggle channel"); channelCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(channelCheckBox); radiosCheckBox = new JCheckBox("Radios", true); radiosCheckBox.setActionCommand("toggle radios"); radiosCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(radiosCheckBox); radioActivityCheckBox = new JCheckBox("Radio Activity", true); radioActivityCheckBox.setActionCommand("toggle radio activity"); radioActivityCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(radioActivityCheckBox); arrowCheckBox = new JCheckBox("Scale arrow", true); arrowCheckBox.setActionCommand("toggle arrow"); arrowCheckBox.addActionListener(selectGraphicsHandler); graphicsComponentsPanel.add(arrowCheckBox); graphicsComponentsPanel.add(Box.createRigidArea(new Dimension(0,20))); graphicsComponentsPanel.add(new JLabel("Configure attenuating obstacles:")); JButton addBackgroundButton = new JButton("Set background image"); addBackgroundButton.setActionCommand("set background image"); addBackgroundButton.addActionListener(setBackgroundHandler); graphicsComponentsPanel.add(addBackgroundButton); JButton analyzeObstaclesButton = new JButton("Analyze background for obstacles"); analyzeObstaclesButton.setActionCommand("analyze for obstacles"); analyzeObstaclesButton.addActionListener(analyzeObstaclesHandler); graphicsComponentsPanel.add(analyzeObstaclesButton); // Create visualize channel output panel JPanel visualizeChannelPanel = new JPanel(); visualizeChannelPanel.setLayout(new BoxLayout(visualizeChannelPanel, BoxLayout.Y_AXIS)); visualizeChannelPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); visualizeChannelPanel.setAlignmentX(Component.CENTER_ALIGNMENT); // Channel coloring intervals visualizeChannelPanel.add(new JLabel("Color intervals:")); JRadioButton fixedColoringButton = new JRadioButton("Fixed channel coloring"); fixedColoringButton.setSelected(true); fixedColoringButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { coloringIsFixed = true; } }); visualizeChannelPanel.add(fixedColoringButton); JRadioButton relativeColoringButton = new JRadioButton("Relative channel coloring"); relativeColoringButton.setSelected(true); relativeColoringButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { coloringIsFixed = false; } }); visualizeChannelPanel.add(relativeColoringButton); ButtonGroup coloringGroup = new ButtonGroup(); coloringGroup.add(fixedColoringButton); coloringGroup.add(relativeColoringButton); coloringIntervalPanel = new JPanel() { public void paintComponent(Graphics g) { super.paintComponent(g); int width = getWidth(); int height = getHeight(); double diff = coloringHighest - coloringLowest; int textHeight = g.getFontMetrics().getHeight(); // If computing if (attenuatorThread != null && attenuatorThread.isAlive()) { g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.setColor(Color.BLACK); String stringToDraw = "[calculating]"; g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); return; } // Check for infinite values if (Double.isInfinite(coloringHighest) || Double.isInfinite(coloringLowest)) { g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.setColor(Color.BLACK); String stringToDraw = "INFINITE VALUES EXIST"; g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); return; } // Check if values are constant if (diff == 0) { g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); g.setColor(Color.BLACK); NumberFormat formatter = DecimalFormat.getNumberInstance(); String stringToDraw = "CONSTANT VALUES (" + formatter.format(coloringHighest) + ")"; g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); return; } for (int i=0; i < width; i++) { double paintValue = coloringLowest + (double) i / (double) width * diff; g.setColor( new Color( getColorOfSignalStrength(paintValue, coloringLowest, coloringHighest))); g.drawLine(i, 0, i, height); } if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { NumberFormat formatter = DecimalFormat.getPercentInstance(); g.setColor(Color.BLACK); g.drawString(formatter.format(coloringLowest), 3, textHeight); String stringToDraw = formatter.format(coloringHighest); g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH || dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR ) { NumberFormat formatter = DecimalFormat.getNumberInstance(); g.setColor(Color.BLACK); g.drawString(formatter.format(coloringLowest) + "dBm", 3, textHeight); String stringToDraw = formatter.format(coloringHighest) + "dBm"; g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR || dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { NumberFormat formatter = DecimalFormat.getNumberInstance(); g.setColor(Color.BLACK); g.drawString(formatter.format(coloringLowest) + "dB", 3, textHeight); String stringToDraw = formatter.format(coloringHighest) + "dB"; g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { NumberFormat formatter = DecimalFormat.getNumberInstance(); g.setColor(Color.BLACK); g.drawString(formatter.format(coloringLowest) + "us", 3, textHeight); String stringToDraw = formatter.format(coloringHighest) + "us"; g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); } } }; coloringIntervalPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); Dimension colorPanelSize = new Dimension(200, 20); coloringIntervalPanel.setPreferredSize(colorPanelSize); coloringIntervalPanel.setMinimumSize(colorPanelSize); coloringIntervalPanel.setMaximumSize(colorPanelSize); coloringIntervalPanel.setAlignmentX(Component.LEFT_ALIGNMENT); visualizeChannelPanel.add(coloringIntervalPanel); // Choose channel output to visualize visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); visualizeChannelPanel.add(new JLabel("Visualize radio output:")); JRadioButton signalStrengthButton = new JRadioButton("Signal strength"); signalStrengthButton.setActionCommand("signalStrengthButton"); signalStrengthButton.setSelected(true); signalStrengthButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH; } }); visualizeChannelPanel.add(signalStrengthButton); JRadioButton signalStrengthVarButton = new JRadioButton("Signal strength variance"); signalStrengthVarButton.setActionCommand("signalStrengthVarButton"); signalStrengthVarButton.setSelected(false); signalStrengthVarButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR; } }); visualizeChannelPanel.add(signalStrengthVarButton); JRadioButton SNRButton = new JRadioButton("Signal to Noise ratio"); SNRButton.setActionCommand("SNRButton"); SNRButton.setSelected(false); SNRButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.SNR; } }); visualizeChannelPanel.add(SNRButton); JRadioButton SNRVarButton = new JRadioButton("Signal to Noise variance"); SNRVarButton.setActionCommand("SNRVarButton"); SNRVarButton.setSelected(false); SNRVarButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.SNR_VAR; } }); visualizeChannelPanel.add(SNRVarButton); JRadioButton probabilityButton = new JRadioButton("Probability of reception"); probabilityButton.setActionCommand("probabilityButton"); probabilityButton.setSelected(false); probabilityButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.PROB_OF_RECEPTION; } }); visualizeChannelPanel.add(probabilityButton); JRadioButton rmsDelaySpreadButton = new JRadioButton("RMS delay spread"); rmsDelaySpreadButton.setActionCommand("rmsDelaySpreadButton"); rmsDelaySpreadButton.setSelected(false); rmsDelaySpreadButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataTypeToVisualize = ChannelModel.TransmissionData.DELAY_SPREAD_RMS; } }); visualizeChannelPanel.add(rmsDelaySpreadButton); visTypeSelectionGroup = new ButtonGroup(); visTypeSelectionGroup.add(signalStrengthButton); visTypeSelectionGroup.add(signalStrengthVarButton); visTypeSelectionGroup.add(SNRButton); visTypeSelectionGroup.add(SNRVarButton); visTypeSelectionGroup.add(probabilityButton); visTypeSelectionGroup.add(rmsDelaySpreadButton); visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); visualizeChannelPanel.add(new JLabel("Image resolution:")); resolutionSlider = new JSlider(JSlider.HORIZONTAL, 30, 600, 200); resolutionSlider.setMajorTickSpacing(100); resolutionSlider.setPaintTicks(true); resolutionSlider.setPaintLabels(true); visualizeChannelPanel.add(resolutionSlider); visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); JButton recalculateVisibleButton = new JButton("Recalculate visible area"); recalculateVisibleButton.setActionCommand("recalculate visible area"); recalculateVisibleButton.addActionListener(formulaHandler); visualizeChannelPanel.add(recalculateVisibleButton); // Create control panel controlPanel = new JPanel(); controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); controlPanel.add(graphicsComponentsPanel); controlPanel.add(new JSeparator()); controlPanel.add(Box.createRigidArea(new Dimension(0, 5))); controlPanel.add(visualizeChannelPanel); controlPanel.setPreferredSize(new Dimension(250,700)); controlPanel.setAlignmentX(Component.CENTER_ALIGNMENT); scrollControlPanel = new JScrollPane( controlPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scrollControlPanel.setPreferredSize(new Dimension(250,0)); // Add everything this.setLayout(new BorderLayout()); this.add(BorderLayout.CENTER, canvas); // Add canvas this.add(BorderLayout.EAST, scrollControlPanel); // Load external images (antenna) Toolkit toolkit = Toolkit.getDefaultToolkit(); URL imageURL = this.getClass().getClassLoader().getResource(antennaImageFilename); antennaImage = toolkit.getImage(imageURL); MediaTracker tracker = new MediaTracker(canvas); tracker.addImage(antennaImage, 1); try { tracker.waitForAll(); } catch (InterruptedException ex) { logger.fatal("Interrupted during image loading, aborting"); return; } // Try to select current plugin try { setSelected(true); } catch (java.beans.PropertyVetoException e) { // Could not select } } /** * Listens to mouse event on canvas */ private MouseListener canvasMouseHandler = new MouseListener() { public void mouseReleased(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { if (inSelectMode) { Vector hitRadios = trackClickedRadio(e.getPoint()); if (hitRadios == null || hitRadios.size() == 0) { if (e.getButton() != MouseEvent.BUTTON1) { selectedRadio = null; channelImage = null; canvas.repaint(); } return; } if (hitRadios.size() == 1 && hitRadios.firstElement() == selectedRadio) return; if (selectedRadio == null || !hitRadios.contains(selectedRadio)) { selectedRadio = hitRadios.firstElement(); } else { // Select next in list selectedRadio = hitRadios.get( (hitRadios.indexOf(selectedRadio)+1) % hitRadios.size() ); } channelImage = null; canvas.repaint(); } else if (inTrackMode && selectedRadio != null) { // Calculate real clicked position double realClickedX = e.getX() / currentZoomX - currentPanX; double realClickedY = e.getY() / currentZoomY - currentPanY; Position radioPosition = currentRadioMedium.getRadioPosition(selectedRadio); final double radioX = radioPosition.getXCoordinate(); final double radioY = radioPosition.getYCoordinate(); trackedComponents = currentChannelModel.getRaysOfTransmission(radioX, radioY, realClickedX, realClickedY); canvas.repaint(); } } public void mouseEntered(MouseEvent e) { } public void mousePressed(MouseEvent e) { lastHandledPosition = new Point(e.getX(), e.getY()); // Set zoom center (real world) zoomCenterX = e.getX() / currentZoomX - currentPanX; zoomCenterY = e.getY() / currentZoomY - currentPanY; zoomCenterPoint = e.getPoint(); } }; /** * Listens to mouse movements when in pan mode */ private MouseMotionListener canvasPanModeHandler = new MouseMotionListener() { public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { if (lastHandledPosition == null) { lastHandledPosition = e.getPoint(); return; } // Pan relative to mouse movement and current zoom // This way the mouse "lock" to the canvas currentPanX += ((float) (e.getX() - lastHandledPosition.x)) / currentZoomX; currentPanY += ((float) (e.getY() - lastHandledPosition.y)) / currentZoomY; lastHandledPosition = e.getPoint(); canvas.repaint(); } }; /** * Listens to mouse movements when in zoom mode */ private MouseMotionListener canvasZoomModeHandler = new MouseMotionListener() { public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { if (lastHandledPosition == null) { lastHandledPosition = e.getPoint(); return; } // Zoom relative to mouse movement (keep XY-proportions) currentZoomY += 0.005 * currentZoomY * ((double) (lastHandledPosition.y - e.getY())); currentZoomY = Math.max(0.05, currentZoomY); currentZoomY = Math.min(1500, currentZoomY); currentZoomX = currentZoomY; // We also need to update the current pan in order to zoom towards the mouse currentPanX = zoomCenterPoint.x/currentZoomX - zoomCenterX; currentPanY = zoomCenterPoint.y/currentZoomY - zoomCenterY; lastHandledPosition = e.getPoint(); canvas.repaint(); } }; /** * Selects which mouse mode the canvas should be in (select/pan/zoom) */ private ActionListener canvasModeHandler = new ActionListener() { public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("set select mode")) { // Select mode, no mouse motion listener needed for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) canvas.removeMouseMotionListener(reggedListener); inTrackMode = false; inSelectMode = true; } else if (e.getActionCommand().equals("set pan mode")) { // Remove all other mouse motion listeners for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) canvas.removeMouseMotionListener(reggedListener); inSelectMode = false; inTrackMode = false; // Add the special pan mouse motion listener canvas.addMouseMotionListener(canvasPanModeHandler); } else if (e.getActionCommand().equals("set zoom mode")) { // Remove all other mouse motion listeners for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) canvas.removeMouseMotionListener(reggedListener); inSelectMode = false; inTrackMode = false; // Add the special zoom mouse motion listener canvas.addMouseMotionListener(canvasZoomModeHandler); } else if (e.getActionCommand().equals("set track rays mode")) { // Remove all other mouse motion listeners for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) canvas.removeMouseMotionListener(reggedListener); inSelectMode = false; inTrackMode = true; } else if (e.getActionCommand().equals("toggle show settings")) { if (((JCheckBox) e.getSource()).isSelected()) scrollControlPanel.setVisible(true); else scrollControlPanel.setVisible(false); thisPlugin.invalidate(); thisPlugin.revalidate(); } } }; /** * Selects which graphical parts should be painted */ private ActionListener selectGraphicsHandler = new ActionListener() { public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("toggle background")) drawBackgroundImage = ((JCheckBox) e.getSource()).isSelected(); else if (e.getActionCommand().equals("toggle obstacles")) drawCalculatedObstacles = ((JCheckBox) e.getSource()).isSelected(); else if (e.getActionCommand().equals("toggle channel")) drawChannelProbabilities = ((JCheckBox) e.getSource()).isSelected(); else if (e.getActionCommand().equals("toggle radios")) drawRadios = ((JCheckBox) e.getSource()).isSelected(); else if (e.getActionCommand().equals("toggle radio activity")) drawRadioActivity = ((JCheckBox) e.getSource()).isSelected(); else if (e.getActionCommand().equals("toggle arrow")) drawScaleArrow = ((JCheckBox) e.getSource()).isSelected(); canvas.repaint(); } }; /** * Helps user set a background image which can be analysed for obstacles/freespace. */ private ActionListener setBackgroundHandler = new ActionListener() { /** * Choosable file filter that supports tif, gif, jpg, png, bmp. */ class ImageFilter extends FileFilter { public boolean accept(File f) { if (f.isDirectory()) { return true; } String filename = f.getName(); if (filename != null) { if (filename.endsWith(".tiff") || filename.endsWith(".tif") || filename.endsWith(".gif") || filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png") || filename.endsWith(".bmp")) { return true; } } return false; } public String getDescription() { return "All supported images"; } } class ImageSettingsDialog extends JDialog { private double virtualStartX = 0.0, virtualStartY = 0.0, virtualWidth = 0.0, virtualHeight = 0.0; private JFormattedTextField virtualStartXField, virtualStartYField, virtualWidthField, virtualHeightField; private boolean terminatedOK = false; private NumberFormat doubleFormat = NumberFormat.getNumberInstance(); /** * Creates a new dialog for settings background parameters */ protected ImageSettingsDialog(File imageFile, Image image, Frame owner) { super(owner, "Image settings"); JPanel tempPanel; setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // Set layout and add components doubleFormat.setMinimumIntegerDigits(1); setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS)); tempPanel = new JPanel(new GridLayout(1, 2)); tempPanel.add(new JLabel("Start X (m) ")); virtualStartXField = new JFormattedTextField(doubleFormat); virtualStartXField.setValue(new Double(0.0)); tempPanel.add(virtualStartXField); add(tempPanel); tempPanel = new JPanel(new GridLayout(1, 2)); tempPanel.add(new JLabel("Start Y (m)")); virtualStartYField = new JFormattedTextField(doubleFormat); virtualStartYField.setValue(new Double(0.0)); tempPanel.add(virtualStartYField); add(tempPanel); tempPanel = new JPanel(new GridLayout(1, 2)); tempPanel.add(new JLabel("Width (m)")); virtualWidthField = new JFormattedTextField(doubleFormat); virtualWidthField.setValue(new Double(100.0)); tempPanel.add(virtualWidthField); add(tempPanel); tempPanel = new JPanel(new GridLayout(1, 2)); tempPanel.add(new JLabel("Height (m)")); virtualHeightField = new JFormattedTextField(doubleFormat); virtualHeightField.setValue(new Double(100.0)); tempPanel.add(virtualHeightField); add(tempPanel); add(Box.createVerticalGlue()); add(Box.createVerticalStrut(10)); tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(Box.createHorizontalGlue()); final JButton okButton = new JButton("OK"); this.getRootPane().setDefaultButton(okButton); final JButton cancelButton = new JButton("Cancel"); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { virtualStartX = ((Number) virtualStartXField.getValue()).doubleValue(); virtualStartY = ((Number) virtualStartYField.getValue()).doubleValue(); virtualWidth = ((Number) virtualWidthField.getValue()).doubleValue(); virtualHeight = ((Number) virtualHeightField.getValue()).doubleValue(); terminatedOK = true; dispose(); } }); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { terminatedOK = false; dispose(); } }); tempPanel.add(okButton); tempPanel.add(cancelButton); add(tempPanel); // Show dialog setModal(true); pack(); setLocationRelativeTo(owner); setVisible(true); } public boolean terminatedOK() { return terminatedOK; } public double getVirtualStartX() { return virtualStartX; } public double getVirtualStartY() { return virtualStartY; } public double getVirtualWidth() { return virtualWidth; } public double getVirtualHeight() { return virtualHeight; } } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("set background image")) { // Let user select image file JFileChooser fileChooser = new JFileChooser(); ImageFilter filter = new ImageFilter(); fileChooser.addChoosableFileFilter(filter); int returnVal = fileChooser.showOpenDialog(canvas); if (returnVal != JFileChooser.APPROVE_OPTION) { // User cancelled return; } File file = fileChooser.getSelectedFile(); // User selected non-supported file, aborting if (!filter.accept(file)) { logger.fatal("Non-supported file type, aborting"); return; } logger.info("Opening: " + file.getName() + "."); // Load image using toolkit and media tracker Toolkit toolkit = Toolkit.getDefaultToolkit(); Image image = toolkit.getImage(file.getAbsolutePath()); MediaTracker tracker = new MediaTracker(canvas); tracker.addImage(image, 1); try { tracker.waitForAll(); if (tracker.isErrorAny()) { logger.fatal("Error when loading image: "); image = null; } if (image == null) { logger.fatal("Image is null, aborting"); return; } } catch (InterruptedException ex) { logger.fatal("Interrupted during image loading, aborting"); return; } // Let user set virtual size of loaded image ImageSettingsDialog dialog = new ImageSettingsDialog(file, image, GUI.frame); if (!dialog.terminatedOK()) { logger.fatal("User cancelled, aborting"); return; } // Add background image backgroundStartX = dialog.getVirtualStartX(); backgroundStartY = dialog.getVirtualStartY(); backgroundWidth = dialog.getVirtualWidth(); backgroundHeight = dialog.getVirtualHeight(); backgroundImage = image; backgroundImageFile = file; } } }; /** * Helps user analyze a background for obstacles. */ private ActionListener analyzeObstaclesHandler = new ActionListener() { class AnalyzeImageDialog extends JDialog { private NumberFormat intFormat = NumberFormat.getIntegerInstance(); private BufferedImage imageToAnalyze = null; private BufferedImage obstacleImage = null; private JPanel canvasPanel = null; private boolean[][] obstacleArray = null; private boolean exitedOK = false; private JSlider redSlider, greenSlider, blueSlider, toleranceSlider, sizeSlider; /** * Listens to preview mouse motion event (when picking color) */ private MouseMotionListener myMouseMotionListener = new MouseMotionListener() { public void mouseDragged(MouseEvent e) { } public void mouseMoved(MouseEvent e) { // Convert from mouse to image pixel position Point pixelPoint = new Point( (int) (e.getX() * (((double) imageToAnalyze.getWidth()) / ((double) canvasPanel.getWidth()))), (int) (e.getY() * (((double) imageToAnalyze.getHeight()) / ((double) canvasPanel.getHeight()))) ); // Fetch color int color = imageToAnalyze.getRGB(pixelPoint.x, pixelPoint.y); int red = (color & 0x00ff0000) >> 16; int green = (color & 0x0000ff00) >> 8; int blue = color & 0x000000ff; // Update sliders redSlider.setValue(red); redSlider.repaint(); greenSlider.setValue(green); greenSlider.repaint(); blueSlider.setValue(blue); blueSlider.repaint(); } }; /** * Listens to preview mouse event (when picking color) */ private MouseListener myMouseListener = new MouseListener() { public void mouseClicked(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { // Stop picking color again; remove mouse listeners and reset mouse cursor MouseListener[] allMouseListeners = canvasPanel.getMouseListeners(); for (MouseListener mouseListener: allMouseListeners) canvasPanel.removeMouseListener(mouseListener); MouseMotionListener[] allMouseMotionListeners = canvasPanel.getMouseMotionListeners(); for (MouseMotionListener mouseMotionListener: allMouseMotionListeners) canvasPanel.removeMouseMotionListener(mouseMotionListener); canvasPanel.setCursor(Cursor.getDefaultCursor()); } }; /** * Creates a new dialog for settings background parameters */ protected AnalyzeImageDialog(Image currentImage, ChannelModel currentChannelModel, Frame owner) { super(owner, "Analyze for obstacles"); JPanel tempPanel; JLabel tempLabel; JSlider tempSlider; JButton tempButton; setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Dimension labelDimension = new Dimension(100, 20); // Convert Image to BufferedImage imageToAnalyze = new BufferedImage( currentImage.getWidth(this), currentImage.getHeight(this), BufferedImage.TYPE_INT_ARGB ); Graphics2D g = imageToAnalyze.createGraphics(); g.drawImage(currentImage, 0, 0, null); // Prepare initial obstacle image obstacleImage = new BufferedImage( currentImage.getWidth(this), currentImage.getHeight(this), BufferedImage.TYPE_INT_ARGB ); // Set layout and add components intFormat.setMinimumIntegerDigits(1); intFormat.setMaximumIntegerDigits(3); intFormat.setParseIntegerOnly(true); setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS)); // Obstacle color tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Obstacle")); tempLabel.setPreferredSize(labelDimension); add(tempPanel); tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Red")); tempLabel.setPreferredSize(labelDimension); tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); tempSlider.setMajorTickSpacing(50); tempSlider.setPaintTicks(true); tempSlider.setPaintLabels(true); add(tempPanel); redSlider = tempSlider; tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Green")); tempLabel.setPreferredSize(labelDimension); tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); tempSlider.setMajorTickSpacing(50); tempSlider.setPaintTicks(true); tempSlider.setPaintLabels(true); add(tempPanel); greenSlider = tempSlider; tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Blue")); tempLabel.setPreferredSize(labelDimension); tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); tempSlider.setMajorTickSpacing(50); tempSlider.setPaintTicks(true); tempSlider.setPaintLabels(true); add(tempPanel); blueSlider = tempSlider; // Tolerance tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Tolerance")); tempLabel.setPreferredSize(labelDimension); tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 128, 0)); tempSlider.setMajorTickSpacing(25); tempSlider.setPaintTicks(true); tempSlider.setPaintLabels(true); add(tempPanel); toleranceSlider = tempSlider; // Obstacle size tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(tempLabel = new JLabel("Obstacle size")); tempLabel.setPreferredSize(labelDimension); tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 1, 40, 40)); tempSlider.setInverted(true); tempSlider.setMajorTickSpacing(5); tempSlider.setPaintTicks(true); tempSlider.setPaintLabels(true); add(tempPanel); sizeSlider = tempSlider; // Buttons: Pick color, Preview obstacles etc. tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(Box.createHorizontalGlue()); tempPanel.add(tempButton = new JButton("Pick color")); tempButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Set to color picker mode (if not already there) if (canvasPanel.getMouseMotionListeners().length == 0) { canvasPanel.addMouseListener(myMouseListener); canvasPanel.addMouseMotionListener(myMouseMotionListener); canvasPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } } }); tempPanel.add(Box.createHorizontalStrut(5)); tempPanel.add(tempButton = new JButton("Preview obstacles")); tempButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { obstacleImage = createObstacleImage(); canvasPanel.repaint(); } }); add(tempPanel); add(Box.createVerticalStrut(10)); // Preview image tempPanel = new JPanel() { public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(imageToAnalyze, 0, 0, getWidth(), getHeight(), this); g.drawImage(obstacleImage, 0, 0, getWidth(), getHeight(), this); } }; tempPanel.setBorder( BorderFactory.createTitledBorder( BorderFactory.createLineBorder(Color.BLACK), "Preview")); tempPanel.setPreferredSize(new Dimension(400, 400)); tempPanel.setBackground(Color.CYAN); add(tempPanel); canvasPanel = tempPanel; // Saved in canvasPanel // Buttons: Cancel, OK tempPanel = new JPanel(); tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); tempPanel.add(Box.createHorizontalGlue()); tempPanel.add(tempButton = new JButton("Cancel")); tempButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); } }); tempPanel.add(Box.createHorizontalStrut(5)); tempPanel.add(tempButton = new JButton("OK")); tempButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { obstacleImage = createObstacleImage(); exitedOK = true; dispose(); } }); add(tempPanel); add(Box.createVerticalGlue()); add(Box.createVerticalStrut(10)); // Show dialog setModal(true); pack(); setLocationRelativeTo(owner); setVisible(true); } /** * Create obstacle image by analyzing current background image * and using the current obstacle color, size and tolerance. * This method also creates the boolean array obstacleArray. * * @return New obstacle image */ private BufferedImage createObstacleImage() { int nrObstacles = 0; // Create new obstacle image all transparent (no obstacles) BufferedImage newObstacleImage = new BufferedImage( imageToAnalyze.getWidth(), imageToAnalyze.getHeight(), BufferedImage.TYPE_INT_ARGB ); for (int x=0; x < imageToAnalyze.getWidth(); x++) { for (int y=0; y < imageToAnalyze.getHeight(); y++) { newObstacleImage.setRGB(x, y, 0x00000000); } } // Get target color to match against int targetRed = redSlider.getValue(); int targetGreen = greenSlider.getValue(); int targetBlue = blueSlider.getValue(); // Get obstacle resolution and size int boxSize = sizeSlider.getValue(); int tolerance = toleranceSlider.getValue(); // Divide image into boxes and check each box for obstacles int arrayWidth = (int) Math.ceil((double) imageToAnalyze.getWidth() / (double) boxSize); int arrayHeight = (int) Math.ceil((double) imageToAnalyze.getHeight() / (double) boxSize); obstacleArray = new boolean[arrayWidth][arrayHeight]; for (int x=0; x < imageToAnalyze.getWidth(); x+=boxSize) { for (int y=0; y < imageToAnalyze.getHeight(); y+=boxSize) { boolean boxIsObstacle = false; // Check all pixels in box for obstacles for (int xx=x; xx < x + boxSize && xx < imageToAnalyze.getWidth(); xx++) { for (int yy=y; yy < y + boxSize && yy < imageToAnalyze.getHeight(); yy++) { // Get current pixel color int color = imageToAnalyze.getRGB(xx, yy); int red = (color & 0x00ff0000) >> 16; int green = (color & 0x0000ff00) >> 8; int blue = color & 0x000000ff; // Calculate difference from target color int difference = Math.abs(red - targetRed) + Math.abs(green - targetGreen) + Math.abs(blue - targetBlue); // If difference is small enough make this box an obstacle if (difference <= tolerance) { boxIsObstacle = true; break; } } if (boxIsObstacle) break; } // If box is obstacle, colorize it if (boxIsObstacle) { obstacleArray[x/boxSize][y/boxSize] = true; nrObstacles++; // Colorize all pixels in the box for (int xx=x; xx < x + boxSize && xx < imageToAnalyze.getWidth(); xx++) { for (int yy=y; yy < y + boxSize && yy < imageToAnalyze.getHeight(); yy++) { newObstacleImage.setRGB(xx, yy, 0x9922ff22); } } } else obstacleArray[x/boxSize][y/boxSize] = false; } } // End of "divide into boxes" for-loop return newObstacleImage; } } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("analyze for obstacles")) { if (backgroundImage == null) return; AnalyzeImageDialog analyzer = new AnalyzeImageDialog( backgroundImage, currentChannelModel, GUI.frame ); // Check return value from analyzer if (analyzer.exitedOK) { // Remove old obstacle and register new final boolean[][] obstacleArray = analyzer.obstacleArray; final int boxSize = analyzer.sizeSlider.getValue(); // Create progress monitor final ProgressMonitor pm = new ProgressMonitor( GUI.frame, "Registering obstacles", null, 0, obstacleArray.length - 1 ); // Thread that will perform the work final Runnable runnable = new Runnable() { public void run() { try { int foundObstacles = 0; // Clear all old obstacles currentChannelModel.removeAllObstacles(); for (int x=0; x < obstacleArray.length; x++) { for (int y=0; y < (obstacleArray[0]).length; y++) { // If obstacle, register it if (obstacleArray[x][y]) { double realWidth = ((double) boxSize * backgroundWidth) / (double) backgroundImage.getWidth(null); double realHeight = ((double) boxSize * backgroundHeight) / (double) backgroundImage.getHeight(null); double realStartX = backgroundStartX + (double) x * realWidth; double realStartY = backgroundStartY + (double) y * realHeight; foundObstacles++; if (realStartX + realWidth > backgroundStartX + backgroundWidth) realWidth = backgroundStartX + backgroundWidth - realStartX; if (realStartY + realHeight > backgroundStartY + backgroundHeight) realHeight = backgroundStartY + backgroundHeight - realStartY; currentChannelModel.addRectObstacle( realStartX, realStartY, realWidth, realHeight, false ); } } // Check if the dialog has been cancelled if (pm.isCanceled()) return; // Update progress pm.setProgress(x); pm.setNote("After/Before merging: " + currentChannelModel.getNumberOfObstacles() + "/" + foundObstacles); } currentChannelModel.notifySettingsChanged(); thisPlugin.repaint(); } catch (Exception ex) { if (pm.isCanceled()) return; logger.fatal("Obstacle adding aborted..: "); ex.printStackTrace(); pm.close(); } pm.close(); } }; // Start thread Thread thread = new Thread(runnable); thread.start(); } } } }; /** * Listens to settings changes in the radio medium. */ private Observer radioMediumSettingsObserver = new Observer() { public void update(Observable obs, Object obj) { // Clear selected radio (if any selected) and radio medium coverage selectedRadio = null; channelImage = null; canvas.repaint(); } }; /** * Listens to settings changes in the radio medium. */ private Observer radioMediumActivityObserver = new Observer() { public void update(Observable obs, Object obj) { // Just remove any selected radio (it may have been removed) canvas.repaint(); } }; /** * Listens to settings changes in the channel model. */ private Observer channelModelSettingsObserver = new Observer() { public void update(Observable obs, Object obj) { needToRepaintObstacleImage = true; canvas.repaint(); } }; /** * Returns a color corresponding to given value where higher values are more green, and lower values are more red. * * @param value Signal strength of received signal (dB) * @param lowBorder * @param highBorder * @return Integer containing standard ARGB color. */ private int getColorOfSignalStrength(double value, double lowBorder, double highBorder) { double upperLimit = highBorder; // Best signal adds green double lowerLimit = lowBorder; // Bad signal adds red double intervalSize = (upperLimit - lowerLimit) / 2; double middleValue = lowerLimit + (upperLimit - lowerLimit) / 2; if (value > highBorder) { return 0xCC00FF00; } if (value < lowerLimit) { return 0xCCFF0000; } int red = 0, green = 0, blue = 0, alpha = 0xCC; // Upper limit (green) if (value > upperLimit - intervalSize) { green = (int) (255 - 255*(upperLimit - value)/intervalSize); } // Medium signal adds blue if (value > middleValue - intervalSize && value < middleValue + intervalSize) { blue = (int) (255 - 255*Math.abs(middleValue - value)/intervalSize); } // Bad signal adds red if (value < lowerLimit + intervalSize) { red = (int) (255 - 255*(value - lowerLimit)/intervalSize); } return (alpha << 24) | (red << 16) | (green << 8) | blue; } /** * Helps user adjust and calculate the channel propagation formula */ private ActionListener formulaHandler = new ActionListener() { public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("recalculate visible area")) { // Get resolution of new image final Dimension resolution = new Dimension( resolutionSlider.getValue(), resolutionSlider.getValue() ); // Abort if no radio selected if (selectedRadio == null) { channelImage = null; canvas.repaint(); return; } // Get new location/size of area to attenuate final double startX = -currentPanX; final double startY = -currentPanY; final double width = canvas.getWidth() / currentZoomX; final double height = canvas.getHeight() / currentZoomY; // Get sending radio position Position radioPosition = currentRadioMedium.getRadioPosition(selectedRadio); final double radioX = radioPosition.getXCoordinate(); final double radioY = radioPosition.getYCoordinate(); // Create temporary image final BufferedImage tempChannelImage = new BufferedImage(resolution.width, resolution.height, BufferedImage.TYPE_INT_ARGB); // Save time for later analysis final long timeBeforeCalculating = System.currentTimeMillis(); // Create progress monitor final ProgressMonitor pm = new ProgressMonitor( GUI.frame, "Calculating channel attenuation", null, 0, resolution.width - 1 ); // Thread that will perform the work final Runnable runnable = new Runnable() { public void run() { try { // Available signal strength intervals double lowestImageValue = Double.MAX_VALUE; double highestImageValue = -Double.MAX_VALUE; // Create image values (calculate each pixel) double[][] imageValues = new double[resolution.width][resolution.height]; for (int x=0; x < resolution.width; x++) { for (int y=0; y < resolution.height; y++) { if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH) { // Attenuate double[] signalStrength = currentChannelModel.getReceivedSignalStrength( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height ); // Collecting signal strengths if (signalStrength[0] < lowestImageValue) lowestImageValue = signalStrength[0]; if (signalStrength[0] > highestImageValue) highestImageValue = signalStrength[0]; imageValues[x][y] = signalStrength[0]; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR) { // Attenuate double[] signalStrength = currentChannelModel.getReceivedSignalStrength( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height ); // Collecting variances if (signalStrength[1] < lowestImageValue) lowestImageValue = signalStrength[1]; if (signalStrength[1] > highestImageValue) highestImageValue = signalStrength[1]; imageValues[x][y] = signalStrength[1]; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR) { // Get signal to noise ratio double[] snr = currentChannelModel.getSINR( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height, -Double.MAX_VALUE ); // Collecting signal to noise ratio if (snr[0] < lowestImageValue) lowestImageValue = snr[0]; if (snr[0] > highestImageValue) highestImageValue = snr[0]; imageValues[x][y] = snr[0]; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { // Get signal to noise ratio double[] snr = currentChannelModel.getSINR( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height, -Double.MAX_VALUE ); // Collecting variances if (snr[1] < lowestImageValue) lowestImageValue = snr[1]; if (snr[1] > highestImageValue) highestImageValue = snr[1]; imageValues[x][y] = snr[1]; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { // Get probability of receiving a packet TODO What size? Does it matter? double probability = currentChannelModel.getProbability( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height, -Double.MAX_VALUE )[0]; // Collecting variances if (probability < lowestImageValue) lowestImageValue = probability; if (probability > highestImageValue) highestImageValue = probability; imageValues[x][y] = probability; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { // Get RMS delay spread of receiving a packet double rmsDelaySpread = currentChannelModel.getRMSDelaySpread( radioX, radioY, startX + width * x/(double) resolution.width, startY + height * y/(double) resolution.height ); // Collecting variances if (rmsDelaySpread < lowestImageValue) lowestImageValue = rmsDelaySpread; if (rmsDelaySpread > highestImageValue) highestImageValue = rmsDelaySpread; imageValues[x][y] = rmsDelaySpread; } // Check if the dialog has been cancelled if (pm.isCanceled()) return; // Update progress pm.setProgress(x); } } // Adjust coloring signal strength limit if (coloringIsFixed) { if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH) { lowestImageValue = -100; highestImageValue = 0; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR) { lowestImageValue = 0; highestImageValue = 20; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR) { lowestImageValue = -10; highestImageValue = 30; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { lowestImageValue = 0; highestImageValue = 20; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { lowestImageValue = 0; highestImageValue = 1; } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { lowestImageValue = 0; highestImageValue = 5; } } // Save coloring high-low interval coloringHighest = highestImageValue; coloringLowest = lowestImageValue; // Create image for (int x=0; x < resolution.width; x++) { for (int y=0; y < resolution.height; y++) { tempChannelImage.setRGB( x, y, getColorOfSignalStrength(imageValues[x][y], lowestImageValue, highestImageValue) ); } } logger.info("Attenuating area done, time=" + (System.currentTimeMillis() - timeBeforeCalculating)); // Repaint to show the new channel propagation channelStartX = startX; channelStartY = startY; channelWidth = width; channelHeight = height; channelImage = tempChannelImage; thisPlugin.repaint(); coloringIntervalPanel.repaint(); } catch (Exception ex) { if (pm.isCanceled()) return; logger.fatal("Attenuation aborted: " + ex); ex.printStackTrace(); pm.close(); } pm.close(); } }; // Start thread attenuatorThread = new Thread(runnable); attenuatorThread.start(); } } }; /** * Repaint the canvas * @param g2d Current graphics to paint on */ protected void repaintCanvas(Graphics2D g2d) { AffineTransform originalTransform = g2d.getTransform(); // Create "real-world" transformation (scaled 100 times to reduce double->int rounding errors) g2d.scale(currentZoomX, currentZoomY); g2d.translate(currentPanX, currentPanY); AffineTransform realWorldTransform = g2d.getTransform(); g2d.scale(0.01, 0.01); AffineTransform realWorldTransformScaled = g2d.getTransform(); // -- Draw background image if any -- if (drawBackgroundImage && backgroundImage != null) { g2d.setTransform(realWorldTransformScaled); g2d.drawImage(backgroundImage, (int) ((double) backgroundStartX * 100.0), (int) ((double) backgroundStartY * 100.0), (int) ((double) backgroundWidth * 100.0), (int) ((double) backgroundHeight * 100.0), this); } // -- Draw calculated obstacles -- if (drawCalculatedObstacles) { // (Re)create obstacle image if needed if (obstacleImage == null || needToRepaintObstacleImage) { // Abort if no obstacles exist if (currentChannelModel.getNumberOfObstacles() > 0) { // Get bounds of obstacles obstacleStartX = currentChannelModel.getObstacle(0).getMinX(); obstacleStartY = currentChannelModel.getObstacle(0).getMinY(); obstacleWidth = currentChannelModel.getObstacle(0).getMaxX(); obstacleHeight = currentChannelModel.getObstacle(0).getMaxY(); double tempVal = 0; for (int i=0; i < currentChannelModel.getNumberOfObstacles(); i++) { if ((tempVal = currentChannelModel.getObstacle(i).getMinX()) < obstacleStartX) obstacleStartX = tempVal; if ((tempVal = currentChannelModel.getObstacle(i).getMinY()) < obstacleStartY) obstacleStartY = tempVal; if ((tempVal = currentChannelModel.getObstacle(i).getMaxX()) > obstacleWidth) obstacleWidth = tempVal; if ((tempVal = currentChannelModel.getObstacle(i).getMaxY()) > obstacleHeight) obstacleHeight = tempVal; } obstacleWidth -= obstacleStartX; obstacleHeight -= obstacleStartY; // Create new obstacle image BufferedImage tempObstacleImage; if (backgroundImage != null) tempObstacleImage = new BufferedImage( Math.max(600, backgroundImage.getWidth(null)), Math.max(600, backgroundImage.getHeight(null)), BufferedImage.TYPE_INT_ARGB ); else tempObstacleImage = new BufferedImage( 600, 600, BufferedImage.TYPE_INT_ARGB ); Graphics2D obstacleGraphics = (Graphics2D) tempObstacleImage.getGraphics(); // Set real world transform obstacleGraphics.scale( (double) tempObstacleImage.getWidth()/obstacleWidth, (double) tempObstacleImage.getHeight()/obstacleHeight ); obstacleGraphics.translate(-obstacleStartX, -obstacleStartY); // Paint all obstacles obstacleGraphics.setColor(new Color(0, 0, 0, 128)); // DEBUG: Use random obstacle color to distinguish different obstacles //Random random = new Random(); for (int i=0; i < currentChannelModel.getNumberOfObstacles(); i++) { //obstacleGraphics.setColor((new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255), 128))); obstacleGraphics.fill(currentChannelModel.getObstacle(i)); } obstacleImage = tempObstacleImage; } else { // No obstacles exist - create dummy obstacle image obstacleStartX = 0; obstacleStartY = 0; obstacleWidth = 1; obstacleHeight = 1; obstacleImage = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB ); } needToRepaintObstacleImage = false; } // Painting in real world coordinates g2d.setTransform(realWorldTransformScaled); g2d.drawImage(obstacleImage, (int) ((double) obstacleStartX * 100.0), (int) ((double) obstacleStartY * 100.0), (int) ((double) obstacleWidth * 100.0), (int) ((double) obstacleHeight * 100.0), this); } // -- Draw channel probabilities if calculated -- if (drawChannelProbabilities && channelImage != null) { g2d.setTransform(realWorldTransformScaled); g2d.drawImage(channelImage, (int) ((double) channelStartX * 100.0), (int) ((double) channelStartY * 100.0), (int) ((double) channelWidth * 100.0), (int) ((double) channelHeight * 100.0), this); } // -- Draw radios -- if (drawRadios) { for (int i=0; i < currentRadioMedium.getRegisteredRadioCount(); i++) { g2d.setStroke(new BasicStroke((float) 0.0)); g2d.setTransform(realWorldTransform); // Translate to real world radio position Radio radio = currentRadioMedium.getRegisteredRadio(i); Position radioPosition = currentRadioMedium.getRadioPosition(radio); g2d.translate( radioPosition.getXCoordinate(), radioPosition.getYCoordinate() ); // Fetch current translation double xPos = g2d.getTransform().getTranslateX(); double yPos = g2d.getTransform().getTranslateY(); // Jump to identity transform and paint without scaling g2d.setTransform(new AffineTransform()); if (selectedRadio == radio) { g2d.setColor(new Color(255, 0, 0, 100)); g2d.fillRect( (int) xPos - antennaImage.getWidth(this)/2, (int) yPos - antennaImage.getHeight(this)/2, (int) antennaImage.getWidth(this), (int) antennaImage.getHeight(this) ); g2d.setColor(Color.BLUE); g2d.drawRect( (int) xPos - antennaImage.getWidth(this)/2, (int) yPos - antennaImage.getHeight(this)/2, (int) antennaImage.getWidth(this), (int) antennaImage.getHeight(this) ); } g2d.drawImage(antennaImage, (int) xPos - antennaImage.getWidth(this)/2, (int) yPos - antennaImage.getHeight(this)/2, this); } } // -- Draw radio activity -- if (drawRadioActivity) { for (RadioConnection connection: currentRadioMedium.getActiveConnections()) { Position sourcePosition = connection.getSource().getPosition(); // Paint scaled (otherwise bad rounding to integers may occur) g2d.setTransform(realWorldTransformScaled); g2d.setStroke(new BasicStroke((float) 0.0)); for (Radio receivingRadio: connection.getDestinations()) { g2d.setColor(Color.GREEN); // Get source and destination coordinates Position destinationPosition = receivingRadio.getPosition(); g2d.draw(new Line2D.Double( sourcePosition.getXCoordinate()*100.0, sourcePosition.getYCoordinate()*100.0, destinationPosition.getXCoordinate()*100.0, destinationPosition.getYCoordinate()*100.0 )); } for (Radio interferedRadio: connection.getInterfered()) { g2d.setColor(Color.RED); // Get source and destination coordinates Position destinationPosition = interferedRadio.getPosition(); g2d.draw(new Line2D.Double( sourcePosition.getXCoordinate()*100.0, sourcePosition.getYCoordinate()*100.0, destinationPosition.getXCoordinate()*100.0, destinationPosition.getYCoordinate()*100.0 )); } g2d.setColor(Color.BLUE); g2d.setTransform(realWorldTransform); g2d.translate( sourcePosition.getXCoordinate(), sourcePosition.getYCoordinate() ); // Fetch current translation double xPos = g2d.getTransform().getTranslateX(); double yPos = g2d.getTransform().getTranslateY(); // Jump to identity transform and paint without scaling g2d.setTransform(new AffineTransform()); g2d.fillOval( (int) xPos, (int) yPos, (int) 5, (int) 5 ); } } // -- Draw scale arrow -- if (drawScaleArrow) { g2d.setStroke(new BasicStroke((float) .0)); g2d.setColor(Color.BLACK); // Decide on scale comparator double currentArrowDistance = 0.1; // in meters if (currentZoomX < canvas.getWidth() / 2) currentArrowDistance = 0.1; // 0.1m if (currentZoomX < canvas.getWidth() / 2) currentArrowDistance = 1; // 1m if (10 * currentZoomX < canvas.getWidth() / 2) currentArrowDistance = 10; // 10m if (100 * currentZoomX < canvas.getWidth() / 2) currentArrowDistance = 100; // 100m if (1000 * currentZoomX < canvas.getWidth() / 2) currentArrowDistance = 1000; // 100m // "Arrow" points int pixelArrowLength = (int) (currentArrowDistance * currentZoomX); int xPoints[] = new int[] { -pixelArrowLength, -pixelArrowLength, -pixelArrowLength, 0, 0, 0 }; int yPoints[] = new int[] { -5, 5, 0, 0, -5, 5 }; // Paint arrow and text g2d.setTransform(originalTransform); g2d.translate(canvas.getWidth() - 120, canvas.getHeight() - 20); g2d.drawString(currentArrowDistance + "m", -30, -10); g2d.drawPolyline(xPoints, yPoints, xPoints.length); } // -- Draw tracked components (if any) -- if (inTrackMode && trackedComponents != null) { g2d.setTransform(realWorldTransformScaled); g2d.setStroke(new BasicStroke((float) 0.0)); Random random = new Random(); for (int i=0; i < trackedComponents.size(); i++) { g2d.setColor(new Color(255, random.nextInt(255), random.nextInt(255), 255)); Line2D originalLine = trackedComponents.get(i); Line2D newLine = new Line2D.Double( originalLine.getX1()*100.0, originalLine.getY1()*100.0, originalLine.getX2()*100.0, originalLine.getY2()*100.0 ); g2d.draw(newLine); } } g2d.setTransform(originalTransform); } /** * Tracks an on-screen position and returns all hit radios. * May for example be used by a mouse listener to determine * if user clicked on a radio. * * @param clickedPoint On-screen position * @return All hit radios */ protected Vector trackClickedRadio(Point clickedPoint) { Vector hitRadios = new Vector(); if (currentRadioMedium.getRegisteredRadioCount() == 0) return null; double realIconHalfWidth = antennaImage.getWidth(this) / (currentZoomX*2.0); double realIconHalfHeight = antennaImage.getHeight(this) / (currentZoomY*2.0); double realClickedX = clickedPoint.x / currentZoomX - currentPanX; double realClickedY = clickedPoint.y / currentZoomY - currentPanY; for (int i=0; i < currentRadioMedium.getRegisteredRadioCount(); i++) { Radio testRadio = currentRadioMedium.getRegisteredRadio(i); Position testPosition = currentRadioMedium.getRadioPosition(testRadio); if (realClickedX > testPosition.getXCoordinate() - realIconHalfWidth && realClickedX < testPosition.getXCoordinate() + realIconHalfWidth && realClickedY > testPosition.getYCoordinate() - realIconHalfHeight && realClickedY < testPosition.getYCoordinate() + realIconHalfHeight) { hitRadios.add(testRadio); } } if (hitRadios.size() == 0) return null; return hitRadios; } public void closePlugin() { // Remove all our observers if (currentChannelModel != null && channelModelSettingsObserver != null) { currentChannelModel.deleteSettingsObserver(channelModelSettingsObserver); } else { logger.fatal("Could not remove observer: " + channelModelSettingsObserver); } if (currentRadioMedium != null && radioMediumSettingsObserver != null) { currentRadioMedium.deleteSettingsObserver(radioMediumSettingsObserver); } else { logger.fatal("Could not remove observer: " + radioMediumSettingsObserver); } if (currentRadioMedium != null && radioMediumActivityObserver != null) { currentRadioMedium.deleteRadioMediumObserver(radioMediumActivityObserver); } else { logger.fatal("Could not remove observer: " + radioMediumActivityObserver); } } /** * Returns XML elements representing the current configuration. * * @see #setConfigXML(Collection) * @return XML element collection */ public Collection getConfigXML() { Vector config = new Vector(); Element element; // Controls visible element = new Element("controls_visible"); element.setText(Boolean.toString(showSettingsBox.isSelected())); config.add(element); // Viewport element = new Element("zoom_x"); element.setText(Double.toString(currentZoomX)); config.add(element); element = new Element("zoom_y"); element.setText(Double.toString(currentZoomY)); config.add(element); element = new Element("pan_x"); element.setText(Double.toString(currentPanX)); config.add(element); element = new Element("pan_y"); element.setText(Double.toString(currentPanY)); config.add(element); // Components shown element = new Element("show_background"); element.setText(Boolean.toString(drawBackgroundImage)); config.add(element); element = new Element("show_obstacles"); element.setText(Boolean.toString(drawCalculatedObstacles)); config.add(element); element = new Element("show_channel"); element.setText(Boolean.toString(drawChannelProbabilities)); config.add(element); element = new Element("show_radios"); element.setText(Boolean.toString(drawRadios)); config.add(element); element = new Element("show_activity"); element.setText(Boolean.toString(drawRadioActivity)); config.add(element); element = new Element("show_arrow"); element.setText(Boolean.toString(drawScaleArrow)); config.add(element); // Visualization type element = new Element("vis_type"); element.setText(visTypeSelectionGroup.getSelection().getActionCommand()); config.add(element); // Background image if (backgroundImageFile != null) { element = new Element("background_image"); element.setText(backgroundImageFile.getPath()); config.add(element); element = new Element("back_start_x"); element.setText(Double.toString(backgroundStartX)); config.add(element); element = new Element("back_start_y"); element.setText(Double.toString(backgroundStartY)); config.add(element); element = new Element("back_width"); element.setText(Double.toString(backgroundWidth)); config.add(element); element = new Element("back_height"); element.setText(Double.toString(backgroundHeight)); config.add(element); } // Resolution element = new Element("resolution"); element.setText(Integer.toString(resolutionSlider.getValue())); config.add(element); return config; } /** * Sets the configuration depending on the given XML elements. * * @see #getConfigXML() * @param configXML * Config XML elements * @return True if config was set successfully, false otherwise */ public boolean setConfigXML(Collection configXML, boolean visAvailable) { for (Element element : configXML) { if (element.getName().equals("controls_visible")) { showSettingsBox.setSelected(Boolean.parseBoolean(element.getText())); canvasModeHandler.actionPerformed(new ActionEvent(showSettingsBox, ActionEvent.ACTION_PERFORMED, showSettingsBox.getActionCommand())); } else if (element.getName().equals("zoom_x")) { currentZoomX = Double.parseDouble(element.getText()); } else if (element.getName().equals("zoom_y")) { currentZoomY = Double.parseDouble(element.getText()); } else if (element.getName().equals("pan_x")) { currentPanX = Double.parseDouble(element.getText()); } else if (element.getName().equals("pan_y")) { currentPanY = Double.parseDouble(element.getText()); } else if (element.getName().equals("show_background")) { backgroundCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(backgroundCheckBox, ActionEvent.ACTION_PERFORMED, backgroundCheckBox.getActionCommand())); } else if (element.getName().equals("show_obstacles")) { obstaclesCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(obstaclesCheckBox, ActionEvent.ACTION_PERFORMED, obstaclesCheckBox.getActionCommand())); } else if (element.getName().equals("show_channel")) { channelCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(channelCheckBox, ActionEvent.ACTION_PERFORMED, channelCheckBox.getActionCommand())); } else if (element.getName().equals("show_radios")) { radiosCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(radiosCheckBox, ActionEvent.ACTION_PERFORMED, radiosCheckBox.getActionCommand())); } else if (element.getName().equals("show_activity")) { radioActivityCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(radioActivityCheckBox, ActionEvent.ACTION_PERFORMED, radioActivityCheckBox.getActionCommand())); } else if (element.getName().equals("show_arrow")) { arrowCheckBox.setSelected(Boolean.parseBoolean(element.getText())); selectGraphicsHandler.actionPerformed(new ActionEvent(arrowCheckBox, ActionEvent.ACTION_PERFORMED, arrowCheckBox.getActionCommand())); } else if (element.getName().equals("vis_type")) { String visTypeIdentifier = element.getText(); Enumeration buttonEnum = visTypeSelectionGroup.getElements(); while (buttonEnum.hasMoreElements()) { AbstractButton button = buttonEnum.nextElement(); if (button.getActionCommand().equals(visTypeIdentifier)) { visTypeSelectionGroup.setSelected(button.getModel(), true); button.getActionListeners()[0] .actionPerformed(new ActionEvent(button, ActionEvent.ACTION_PERFORMED, button.getActionCommand())); } } } else if (element.getName().equals("background_image")) { backgroundImageFile = new File(element.getText()); if (backgroundImageFile.exists()) { Toolkit toolkit = Toolkit.getDefaultToolkit(); backgroundImage = toolkit.getImage(backgroundImageFile.getAbsolutePath()); MediaTracker tracker = new MediaTracker(canvas); tracker.addImage(backgroundImage, 1); try { tracker.waitForAll(); } catch (InterruptedException ex) { logger.fatal("Interrupted during image loading, aborting"); backgroundImage = null; } } } else if (element.getName().equals("back_start_x")) { backgroundStartX = Double.parseDouble(element.getText()); } else if (element.getName().equals("back_start_y")) { backgroundStartY = Double.parseDouble(element.getText()); } else if (element.getName().equals("back_width")) { backgroundWidth = Double.parseDouble(element.getText()); } else if (element.getName().equals("back_height")) { backgroundHeight = Double.parseDouble(element.getText()); } else if (element.getName().equals("resolution")) { resolutionSlider.setValue(Integer.parseInt(element.getText())); } else { logger.fatal("Unknown configuration value: " + element.getName()); } } canvas.repaint(); return true; } }