package aima.gui.swing.applications.robotics; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.io.File; import java.io.FileInputStream; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileNameExtensionFilter; import aima.core.robotics.IMclRobot; import aima.core.robotics.MonteCarloLocalization; import aima.core.robotics.datatypes.IMclMove; import aima.core.robotics.datatypes.RobotException; import aima.core.robotics.impl.datatypes.AbstractRangeReading; import aima.core.robotics.impl.datatypes.Angle; import aima.core.robotics.impl.datatypes.IPose2D; import aima.core.robotics.impl.map.MclCartesianPlot2D; import aima.core.util.math.geom.shapes.IGeometric2D; import aima.core.util.math.geom.shapes.Rect2D; import aima.gui.swing.applications.robotics.components.AbstractSettingsListener; import aima.gui.swing.applications.robotics.components.IRobotGui; import aima.gui.swing.applications.robotics.components.Settings; import aima.gui.swing.applications.robotics.simple.VirtualRobot; import aima.gui.swing.framework.util.GraphicsTransfer2D; import aima.gui.swing.framework.util.GuiBase; import aima.gui.swing.framework.util.ListTableModel; /** * This generic class provides a graphical user interface for the {@link MonteCarloLocalization} in a two-dimensional environment.<br/> * It makes use of the {@link Settings} class to store and retrieve all its modifiable parameters (e.g. size of the particle cloud).<br/> * Please note that an {@code ISettingsListener} is registered for {@link AbstractSettingsListener}{@code .PARTICLE_COUNT_KEY} in the settings * to be able to change the particle cloud's size while "Auto Locate" is running. Thus one should not register another listener to manage the particle cloud size.<br/> * Additionally, the key {@link AbstractSettingsListener}{@code .MAP_FILE_KEY} is used to store the last used map path in the settings.<br/> * * @author Arno von Borries * @author Jan Phillip Kretzschmar * @author Andreas Walscheid * * @param <P> a pose implementing {@link IPose2D}. * @param <M> a movement (or sequence of movements) of the robot, implementing {@link IMclMove}. * @param <R> a range measurement, implementing {@link AbstractRangeReading}. */ public class GenericMonteCarloLocalization2DApp<P extends IPose2D<P,M>,M extends IMclMove<M>, R extends AbstractRangeReading> { protected ExecutorService backgroundThread = Executors.newSingleThreadExecutor(); protected MonteCarloLocalization<P,Angle,M,AbstractRangeReading> mcl; protected MclCartesianPlot2D<P, M, AbstractRangeReading> map; protected IMclRobot<Angle,M,AbstractRangeReading> robot; protected IRobotGui robotGui; protected Settings settingsGui; protected MclCore core; protected MclGui gui; protected File lastMapFile = null; /** * @param mcl an instance of {@link MonteCarloLocalization} for the type parameters {@code <P>}, {@code Angle}, {@code <M>}, {@code AbstractRangeReading}. * @param map an instance of {@link MclCartesianPlot2D} for the type parameters {@code <P>}, {@code <M>}, {@code AbstractRangeReading}. * @param robot an instance of a class implementing {@link IMclRobot} for the type parameters {@code Angle}, {@code <M>}, {@code AbstractRangeReading}. * @param robotGui an instance of a class implementing {@link IRobotGui} that is able to manage the robot used with this Monte-Carlo-Localization. * @param settingsGui an instance of {@link Settings} which has all those settings loaded that should be accessible for manipulation by the user and * those settings mentioned in this class' documentation. */ public GenericMonteCarloLocalization2DApp(MonteCarloLocalization<P,Angle,M,AbstractRangeReading> mcl, MclCartesianPlot2D<P, M, AbstractRangeReading> map, IMclRobot<Angle,M,AbstractRangeReading> robot, IRobotGui robotGui, Settings settingsGui) { this.mcl = mcl; this.map = map; this.robot = robot; this.robotGui = robotGui; this.settingsGui = settingsGui; this.core = new MclCore(); this.gui = new MclGui(); String mapPath = settingsGui.getSetting(AbstractSettingsListener.MAP_FILE_KEY); if(mapPath != null) this.lastMapFile = new File(mapPath); this.settingsGui.registerListener(AbstractSettingsListener.PARTICLE_COUNT_KEY, this.core); this.settingsGui.registerListener(AbstractSettingsListener.MAX_DISTANCE_KEY, this.core); } /** * Shows the application. */ public void show() { gui.setVisible(true); } /** * Creates the {@code JFrame} of the application and returns it. * @return the main frame of the application. */ public JFrame constructApplicationFrame() { gui.buildPanels(); return gui; } /** * Executes a runnable in the background without freezing the GUI. Therefore it disables and re-enables the GUI's buttons to prevent concurrency. * @param runnable the code to be run in background. */ public void runInBackground(final Runnable runnable) { gui.saveButtonState(); gui.enableButtons(gui.buttonStateInit); backgroundThread.execute(new Runnable() { @Override public void run() { runnable.run(); gui.enableButtons(gui.previousButtonState); } }); } /** * The core manages the particle cloud size and issues commands to the robot whilst updating the GUI according to the results of these commands. * */ protected class MclCore implements Runnable, Settings.ISettingsListener { private static final int stepPause = 100;//ms private JButton button; private Semaphore runningLock = new Semaphore(1); private boolean running = false; private double maxParticleDistance; private int cloudSize; private Set<P> samples; private void setButton(JButton button) { this.button = button; } private void setRunning(boolean running) { try { runningLock.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } this.running = running; runningLock.release(); } private boolean isRunning() { try { runningLock.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } gui.enableButtons(gui.buttonStateInit); final boolean result = running; runningLock.release(); return result; } private synchronized void updateCloudSize(int size) { cloudSize = size; if(map.isLoaded()) { samples = mcl.generateCloud(cloudSize); gui.displaySamples(samples); } } private synchronized void generateParticles() { samples = mcl.generateCloud(cloudSize); gui.displaySamples(samples); } private synchronized void step() throws RobotException, NullPointerException { M move = robot.performMove(); if(move == null) { throw new NullPointerException(); } AbstractRangeReading[] rangeReadings = robot.getRangeReadings(); if(rangeReadings == null) { throw new NullPointerException(); } samples = mcl.localize(samples, move, rangeReadings); P result = map.checkDistanceOfPoses(samples, maxParticleDistance); gui.displayMove(move); gui.displayRangeReadings(rangeReadings); gui.displaySamples(samples); gui.displayResult(result); } /** * Called upon pressing "Auto Locate". This performs the individual steps of the Monte-Carlo-Localization algorithm looped one after the other until * the terminating condition is met, that is, the robot is located with sufficient accuracy. */ @Override public void run() { try { while(running) { step(); try{ Thread.sleep(stepPause); } catch(InterruptedException e) { e.printStackTrace(); } } } catch (NullPointerException e) { /*A NullPointerException may happen if the robot wasn't initialized before.*/ robotGui.notifyInitialize(); } catch (RobotException e) { /*A RobotException may be thrown if the robot disconnected for some reason.*/ } try { runningLock.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } running = false; gui.enableButtons(gui.buttonStateNormal); button.setText(gui.autoLocateTitle); runningLock.release(); } @Override public boolean notifySetting(String key, String value) { if(gui.isVisible()) gui.runNotifyClean.run(); try { if(key.equals(AbstractSettingsListener.PARTICLE_COUNT_KEY)) { updateCloudSize(Integer.parseInt(value)); } else if(key.equals(AbstractSettingsListener.MAX_DISTANCE_KEY)) { maxParticleDistance = Double.parseDouble(value); } else { throw new NumberFormatException(); } } catch(NumberFormatException e) { return false; } return true; } } /** * The main application {@code JFrame}. */ protected class MclGui extends JFrame { private static final long serialVersionUID = 1L; private static final int WINDOW_WIDTH = 800; private static final int WINDOW_HEIGHT = 500; protected final boolean[] buttonStateStart = {true, true, false, false, false, true}; protected final boolean[] buttonStateNormal = {true, true, true, true, true, true}; protected final boolean[] buttonStateInit = {false, false, false, false, false, true}; protected final boolean[] buttonStateAuto = {false, false, false, true, false, true}; private final int clearance = GuiBase.getClearance(); private final String autoLocateTitle = "Auto Locate"; private final String autoLocateStopTitle = "Stop"; protected JButton[] buttons; protected boolean[] previousButtonState; protected JLabel localizationResult; protected JTextArea jtARangeReading; protected ListTableModel movesModel; protected JTable jTMoves; protected JScrollPane movesScrollPane; protected JSlider jSliderZoom; protected JScrollBar horizontalScroll; protected JScrollBar verticalScroll; protected ScrollListener scrollListener = new ScrollListener(); protected MapDrawer md; private double mapWidth = 1.0d; private double mapHeight = 1.0d; private double translateX = 0.0d; private double translateY = 0.0d; private int moveRowHeight; private int horizontalScrollValue; private int verticalScrollValue; /** * Called upon pressing "Initialize Robot". */ protected Runnable runInitRobot = new Runnable() { @Override public void run() { md.drawRobot(robotGui.initializeRobot()); buttons[0].setText(robotGui.getButtonString()); enableButtons(previousButtonState); } }; /** * Called upon pressing "Load Map". */ protected MapLoader mapLoader = new MapLoader(); /** * Called upon pressing "Step". */ protected Runnable runStep = new Runnable() { @Override public void run() { try { core.step(); } catch(NullPointerException e) { /*A NullPointerException may happen if the robot wasn't initialized before.*/ robotGui.notifyInitialize(); } catch(RobotException e) { /*A RobotException may be thrown if the robot disconnected for some reason.*/ } enableButtons(gui.buttonStateNormal); } }; /** * Called upon pressing "Clear GUI". */ protected Runnable runClean = new Runnable() { @Override public void run() { runNotifyClean.run(); core.generateParticles(); enableButtons(gui.buttonStateNormal); } }; protected Runnable runNotifyClean = new Runnable() { @Override public void run() { movesModel.clear(); clearResult(); md.clearMap(); jTMoves.setRowHeight(1); jtARangeReading.setText(""); } }; /** * Creates all components and action listeners for the GUI. */ protected void buildPanels() { setSize(WINDOW_WIDTH, WINDOW_HEIGHT); setMinimumSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)); setTitle("AIMA3e-Java: Monte-Carlo-Localization"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //leftPanel: buttons = new JButton[6]; previousButtonState = new boolean[buttons.length]; buttons[0] = new JButton(IRobotGui.DEFAULT_BUTTON_STRING); buttons[0].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { saveButtonState(); enableButtons(gui.buttonStateInit); backgroundThread.execute(runInitRobot); } }); buttons[1] = new JButton("Load Map"); buttons[1].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { saveButtonState(); enableButtons(gui.buttonStateInit); backgroundThread.execute(mapLoader); } }); buttons[2] = new JButton("Step"); buttons[2].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { enableButtons(gui.buttonStateInit); backgroundThread.execute(runStep); } }); buttons[3] = new JButton(autoLocateTitle); buttons[3].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if(!core.isRunning()) { JButton button = (JButton) arg0.getSource(); button.setText(autoLocateStopTitle); core.setButton(button); core.setRunning(true); if(md.clearResult()) { core.generateParticles(); } enableButtons(buttonStateAuto); backgroundThread.execute(core); } else { core.setRunning(false); } } }); buttons[4] = new JButton("Clear GUI"); buttons[4].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { enableButtons(gui.buttonStateInit); backgroundThread.execute(runClean); } }); buttons[5] = new JButton("Settings"); buttons[5].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { settingsGui.show(); } }); enableButtons(buttonStateStart); localizationResult = new JLabel("<HTML>Result: <BR><BR><BR></HTML>"); JPanel leftPanel = new JPanel(); leftPanel.setBorder(GuiBase.getClearanceBorder()); leftPanel.setLayout(new GridLayout(0,1,clearance,clearance)); for(JButton button:buttons) leftPanel.add(button); leftPanel.add(localizationResult); //centerPanel: md = new MapDrawer(); verticalScroll = new JScrollBar(JScrollBar.VERTICAL); verticalScroll.setEnabled(false); verticalScroll.addAdjustmentListener(scrollListener); horizontalScroll = new JScrollBar(JScrollBar.HORIZONTAL); horizontalScroll.setEnabled(false); horizontalScroll.addAdjustmentListener(scrollListener); jSliderZoom = new JSlider(1, 200); jSliderZoom.setValue(1); jSliderZoom.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { horizontalScrollValue = horizontalScroll.getValue(); verticalScrollValue = verticalScroll.getValue(); md.scaleMap(); } }); JPanel centerPanel = new JPanel(); centerPanel.setBorder(GuiBase.getClearanceBorder()); centerPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH; centerPanel.add(md, c); c.gridx = 1; c.gridy = 0; c.weightx = 0; c.weighty = 0; c.fill = GridBagConstraints.VERTICAL; centerPanel.add(verticalScroll, c); c.gridx = 0; c.gridy = 1; c.weightx = 1; c.weighty = 0; c.fill = GridBagConstraints.HORIZONTAL; centerPanel.add(horizontalScroll, c); c.gridx = 0; c.gridy = 2; c.weightx = 1; c.weighty = 0; c.fill = GridBagConstraints.HORIZONTAL; centerPanel.add(jSliderZoom, c); //rightPanel: JLabel jLRangeReading = new JLabel("Range Reading:"); jtARangeReading = new JTextArea(); jtARangeReading.setEditable(false); jtARangeReading.setLineWrap(true); jtARangeReading.setWrapStyleWord(true); JScrollPane rangeReadingScrollPane = new JScrollPane(jtARangeReading); rangeReadingScrollPane.setAlignmentX(LEFT_ALIGNMENT); jLRangeReading.setLabelFor(rangeReadingScrollPane); JPanel rangeReadingPanel = new JPanel(); rangeReadingPanel.setLayout(new BoxLayout(rangeReadingPanel, BoxLayout.Y_AXIS)); rangeReadingPanel.add(jLRangeReading); rangeReadingPanel.add(GuiBase.getClearanceComp()); rangeReadingPanel.add(rangeReadingScrollPane); movesModel = new ListTableModel("Moves:"); JLabel jLMoves = new JLabel(movesModel.getColumnName(0)); jTMoves = new JTable(movesModel) { private static final long serialVersionUID = 1L; @Override protected void configureEnclosingScrollPane() { } }; movesScrollPane = new JScrollPane(jTMoves, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); jTMoves.setFillsViewportHeight(true); moveRowHeight = jTMoves.getRowHeight(); movesScrollPane.setAlignmentX(LEFT_ALIGNMENT); jLMoves.setLabelFor(movesScrollPane); JPanel movesPanel = new JPanel(); movesPanel.setLayout(new BoxLayout(movesPanel, BoxLayout.Y_AXIS)); movesPanel.add(jLMoves); movesPanel.add(GuiBase.getClearanceComp()); movesPanel.add(movesScrollPane); JPanel rightPanel = new JPanel(); rightPanel.setBorder(GuiBase.getClearanceBorder()); rightPanel.setPreferredSize(new Dimension(170,1)); rightPanel.setLayout(new GridLayout(0,1,clearance,clearance)); rightPanel.add(rangeReadingPanel); rightPanel.add(movesPanel); //Put all panels together: JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add(leftPanel, BorderLayout.WEST); mainPanel.add(centerPanel, BorderLayout.CENTER); mainPanel.add(rightPanel, BorderLayout.EAST); getContentPane().add(mainPanel, BorderLayout.CENTER); //Listen for resizes on the center panel: centerPanel.addComponentListener(new ComponentListener() { @Override public void componentResized(ComponentEvent e) { movesScrollPane.getVerticalScrollBar().setValue(movesScrollPane.getVerticalScrollBar().getMaximum()-movesScrollPane.getVerticalScrollBar().getVisibleAmount()); //Invalidate the values: Scrollbars only repaint on a change of these values! horizontalScrollValue = horizontalScroll.getValue(); verticalScrollValue = verticalScroll.getValue(); horizontalScroll.setValues(-1, 0, -1, -1); verticalScroll.setValues(-1, 0, -1, -1); //Recalculate all scale Factors of the map: md.scaleMap(); } @Override public void componentHidden(ComponentEvent arg0) { } @Override public void componentMoved(ComponentEvent arg0) { } @Override public void componentShown(ComponentEvent arg0) { } }); } /** * (de)activate all buttons. For each button a boolean describing its state is passed. * @param buttonStates one of the four boolean arrays {@code buttonStateStart, buttonStateNormal, buttonStateInit, buttonStateAuto}. */ protected void enableButtons(boolean[] buttonStates) { for(int i=0; i < buttons.length; i++) buttons[i].setEnabled(buttonStates[i]); } /** * Saves the current activation state of all buttons into {@code previousButtonState}. */ protected void saveButtonState() { previousButtonState = new boolean[buttons.length]; for(int i=0; i < buttons.length; i++) previousButtonState[i] = buttons[i].isEnabled(); } /** * Tells the {@link MapDrawer} {@code md} to display the loaded {@link MclCartesianPlot2D} {@code map}. */ protected void createMap() { findMapSize(); if(mapWidth <= 1.0 || mapHeight <= 1.0){ GuiBase.showMessageBox("Map size could not be calculated!"); return; } horizontalScrollValue = 0; verticalScrollValue = 0; md.drawMap(); md.scaleMap(); enableButtons(buttonStateNormal); } /** * Determines the map size by the smallest and greatest values of the shapes to be drawn. */ protected void findMapSize() { Iterator<Rect2D> areaIterator = map.getAreaBoundaries(); Iterator<Rect2D> obstacleIterator = map.getObstacleBoundaries(); double minX = Double.POSITIVE_INFINITY; double maxX = Double.NEGATIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; while(areaIterator.hasNext()) { Rect2D rect = areaIterator.next(); minX = minX > rect.getLowerLeft().getX() ? rect.getLowerLeft().getX() : minX; minY = minY > rect.getLowerLeft().getY() ? rect.getLowerLeft().getY() : minY; maxX = maxX < rect.getUpperRight().getX() ? rect.getUpperRight().getX() : maxX; maxY = maxY < rect.getUpperRight().getY() ? rect.getUpperRight().getY() : maxY; } while(obstacleIterator.hasNext()) { Rect2D rect = obstacleIterator.next(); minX = minX > rect.getLowerLeft().getX() ? rect.getLowerLeft().getX() : minX; minY = minY > rect.getLowerLeft().getY() ? rect.getLowerLeft().getY() : minY; maxX = maxX < rect.getUpperRight().getX() ? rect.getUpperRight().getX() : maxX; maxY = maxY < rect.getUpperRight().getY() ? rect.getUpperRight().getY() : maxY; } mapWidth = maxX - minX; mapHeight = maxY - minY; final double mapBorder = 0.1d * mapWidth > mapHeight ? mapWidth : mapHeight; translateX = -minX + mapBorder; translateY = -minY + mapBorder; mapWidth += 2 * mapBorder; mapHeight += 2 * mapBorder; } /** * Shows a set of {@link AbstractRangeReading}. * @param rangeReadings the range readings to be displayed. */ protected void displayRangeReadings(AbstractRangeReading[] rangeReadings) { StringBuilder ranges = new StringBuilder(); for(AbstractRangeReading rangeReading: rangeReadings) ranges.append(rangeReading.toString()).append("\n"); jtARangeReading.setText(ranges.toString()); } /** * Shows a {@code M} move. * @param move the move to be displayed. */ protected void displayMove(final M move) { final int size = move.toString().split("<BR>").length; final int rowHeight = (int) (size * 1.25d * moveRowHeight); if(rowHeight > jTMoves.getRowHeight()) jTMoves.setRowHeight(rowHeight); movesModel.add("<HTML>" + move.toString() + "</HTML>"); movesScrollPane.getVerticalScrollBar().setValue(movesScrollPane.getVerticalScrollBar().getMaximum()-movesScrollPane.getVerticalScrollBar().getVisibleAmount()); } /** * Displays the sample cloud on the map. * @param samples the set of samples to be displayed. */ protected void displaySamples(Set<P> samples) { md.drawParticles(samples); } /** * Shows the localized position of the robot on the map and in a label. * @param result the position of the robot to be displayed. */ protected void displayResult(P result) { if(result != null) { String resultOutputString = "X: " + GuiBase.getFormat().format(result.getX()) + ",<BR>Y: " + GuiBase.getFormat().format(result.getY()); localizationResult.setText("<HTML>Result: <BR>" + resultOutputString + "</HTML>"); md.showResult(result); } else { clearResult(); } } /** * Clears the result that may has been set. */ protected void clearResult() { md.clearResult(); localizationResult.setText("<HTML>Result: <BR><BR><BR></HTML>"); } /** * The scroll listener is registered on both of the map's scroll bars. It is used to scroll over the map. */ protected class ScrollListener implements AdjustmentListener { private boolean notifyMapDrawer = false; @Override public void adjustmentValueChanged(AdjustmentEvent arg0) { if(notifyMapDrawer) { md.repaint(); } } /** * Sets whether the map should be notified of an adjustment event. * @param b true if the map should be notified. */ protected void setNotify(boolean b) { notifyMapDrawer = b; } } /** * Hands off the loading of a map file to a different thread without freezing the GUI. * Handles exceptions during the loading and processing of a file. */ protected class MapLoader implements Runnable { private JFileChooser chooser; /** * Constructs a new map loader. */ protected MapLoader() { chooser = new JFileChooser(); //Change the background color of the component holding the "Search in:" label. for(int i=0;i<chooser.getComponentCount();i++) { Component component = chooser.getComponent(i); if(component instanceof JComponent) { ((JComponent) component).setBackground(GuiBase.getBackgroundColor()); } } FileNameExtensionFilter filter = new FileNameExtensionFilter( "Map (*.svg)", "svg"); chooser.setFileFilter(filter); GuiBase.initMessageBox(); } @Override public void run() { if(lastMapFile != null) chooser.setSelectedFile(lastMapFile); int returnVal = chooser.showDialog(null, null); if(returnVal == JFileChooser.APPROVE_OPTION) { lastMapFile = chooser.getSelectedFile(); settingsGui.setSetting(AbstractSettingsListener.MAP_FILE_KEY, lastMapFile.getPath()); GuiBase.showMessageBox("Loading map, please wait...", false); try { loadMap(); return; } catch (Exception e) { GuiBase.showMessageBox(e.getMessage()); } } enableButtons(previousButtonState); } /** * Tries to load the map file into the {@link MclCartesianPlot2D} {@code map}. * @throws Exception may be thrown if either the map file is not found or is not valid in some way. */ protected void loadMap() throws Exception { map.loadMap(new FileInputStream(lastMapFile), new FileInputStream(lastMapFile)); if(!map.getAreas().hasNext()) { throw new Exception("Map has no valid area."); } else if(!map.getObstacles().hasNext()) { throw new Exception("Map has no obstacles."); } core.generateParticles(); gui.createMap(); GuiBase.hideMessageBox(); } } /** * This class is a panel where the map, the samples and the robot are displayed. */ protected class MapDrawer extends JPanel { private static final long serialVersionUID = 1L; private static final int POSE_WIDTH = 4; private static final int POSE_HEIGHT = 4; private boolean robotInitialized = false; private boolean mapLoaded = false; private Set<P> samples; private P locResult; private boolean gotResult = false; private double minScaleFactor = 1.0d; private double realScaleFactor = 1.0d; /** * Default constructor. Sets the background color to the GUI foreground color. */ protected MapDrawer() { this.setBackground(GuiBase.getTextColor()); } /** * With this method, the {@code MapDrawer} is told that it can commence the drawing of the map. */ protected void drawMap() { mapLoaded = true; repaint(); } /** *This method clears the panel of any map data. */ protected void deleteMap() { mapLoaded = false; repaint(); } /** * Deletes everything but the map data from the panel. */ protected void clearMap() { samples = null; gotResult = false; repaint(); } /** * Draws the sample cloud. * @param samples the {@code Set} containing the samples. */ protected void drawParticles(Set<P> samples) { this.samples = samples; repaint(); } /** * Draws the localized position of the robot. * @param result the estimated position of the robot. */ protected void showResult(P result) { locResult = result; gotResult = true; repaint(); } /** * Deletes any previous localization result. * @return true if a result was actually deleted. */ protected boolean clearResult() { if(gotResult) { gotResult = false; return true; } return false; } /** * Draws the actual position of the robot. * @param initialized true if the robot is to be drawn. */ protected void drawRobot(boolean initialized) { robotInitialized = initialized; repaint(); } /** * This method calculates the scale factors to scale the map by. */ protected void scaleMap() { final double calcXScaleFactor = (double) getWidth() / mapWidth; final double calcYScaleFactor = (double) getHeight() / mapHeight; if((Double.isInfinite(calcXScaleFactor) && Double.isInfinite(calcYScaleFactor)) || (Double.isNaN(calcXScaleFactor) && Double.isNaN(calcYScaleFactor))) { minScaleFactor = 1.0d; } else if(calcXScaleFactor >= calcYScaleFactor || Double.isNaN(calcXScaleFactor)) { minScaleFactor = calcYScaleFactor - 0.0010d; } else { minScaleFactor = calcXScaleFactor - 0.0005d; } realScaleFactor = jSliderZoom.getValue() == 1 ? minScaleFactor : jSliderZoom.getValue() / 10 + 1; double realMapWidth = mapWidth * realScaleFactor; double realMapHeight = mapHeight * realScaleFactor; scrollListener.setNotify(false); if(realMapWidth <= getWidth()) { horizontalScroll.setEnabled(false); horizontalScroll.setValues(0,0,0,0); } else { realMapWidth *= 1.2d; horizontalScroll.setValues((int) (horizontalScrollValue + getWidth() > realMapWidth ? realMapWidth - getWidth() : horizontalScrollValue), getWidth(), 0, (int) realMapWidth); if(!horizontalScroll.isEnabled()) horizontalScroll.setEnabled(true); } if(realMapHeight <= getHeight()) { verticalScroll.setEnabled(false); verticalScroll.setValues(0,0,0,0); } else { realMapHeight *= 1.2d; verticalScroll.setValues((int) (verticalScrollValue + getHeight() > realMapHeight ? realMapHeight - getHeight() : verticalScrollValue), getHeight(), 0, (int) realMapHeight); if(!verticalScroll.isEnabled()) verticalScroll.setEnabled(true); } scrollListener.setNotify(true); repaint(); } @Override public void paint(Graphics gra) { super.paint(gra); if(mapLoaded) { Graphics2D g2d = (Graphics2D) gra; g2d.translate(translateX*realScaleFactor-horizontalScroll.getValue(),translateY*realScaleFactor-verticalScroll.getValue()); g2d.scale(realScaleFactor, realScaleFactor); //Draw the map: Iterator<IGeometric2D> areaIterator = map.getAreas(); Iterator<IGeometric2D> obstacleIterator = map.getObstacles(); while (areaIterator.hasNext()) { g2d.setColor(GuiBase.getAreaColor()); g2d.draw(GraphicsTransfer2D.transfer(areaIterator.next())); } while (obstacleIterator.hasNext()) { g2d.setColor(GuiBase.getBackgroundColor()); Shape shape = GraphicsTransfer2D.transfer(obstacleIterator.next()); g2d.draw(shape); g2d.fill(shape); } if(samples != null) { //Draw the samples onto the map: for (P sample: samples) { g2d.setColor(Color.GREEN); g2d.fillOval((int) sample.getX() - POSE_WIDTH/2, (int) sample.getY() - POSE_HEIGHT/2, POSE_WIDTH, POSE_HEIGHT); final double h = sample.getHeading(); final int x2 = (int) (sample.getX() + POSE_WIDTH * Math.cos(h)); final int y2 = (int) (sample.getY() + POSE_HEIGHT * Math.sin(h)); g2d.drawLine((int) sample.getX(), (int) sample.getY(), x2, y2); } } if(robotInitialized && robot instanceof VirtualRobot) { g2d.setColor(Color.BLACK); g2d.fillOval((int) ((VirtualRobot) robot).getPose().getX() - POSE_WIDTH/2, (int) ((VirtualRobot) robot).getPose().getY() - POSE_HEIGHT/2, POSE_WIDTH, POSE_HEIGHT); } if(gotResult) { g2d.setColor(Color.BLUE); g2d.drawOval((int) (locResult.getX() - core.maxParticleDistance / 2), (int) (locResult.getY() - core.maxParticleDistance / 2), (int) core.maxParticleDistance, (int) core.maxParticleDistance); } } } } } }