diff --git a/src/main/org/insa/graphics/MainWindow.java b/src/main/org/insa/graphics/MainWindow.java index ac77fcb..a5a29ec 100644 --- a/src/main/org/insa/graphics/MainWindow.java +++ b/src/main/org/insa/graphics/MainWindow.java @@ -53,7 +53,6 @@ import org.insa.algo.weakconnectivity.WeaklyConnectedComponentsAlgorithm; import org.insa.algo.weakconnectivity.WeaklyConnectedComponentsData; import org.insa.graph.Graph; import org.insa.graph.Path; -import org.insa.graph.io.BinaryGraphReaderInsa2016; import org.insa.graph.io.BinaryGraphReaderInsa2018; import org.insa.graph.io.BinaryPathReader; import org.insa.graph.io.GraphReader; @@ -144,19 +143,17 @@ public class MainWindow extends JFrame { @Override public void actionPerformed(ActionEvent e) { StartActionEvent evt = (StartActionEvent) e; - ShortestPathData data = new ShortestPathData(graph, evt.getOrigin(), - evt.getDestination(), evt.getMode()); + ShortestPathData data = new ShortestPathData(graph, evt.getOrigin(), evt.getDestination(), + evt.getMode()); ShortestPathAlgorithm spAlgorithm = null; try { - spAlgorithm = ShortestPathAlgorithmFactory - .createAlgorithm(evt.getAlgorithmClass(), data); + spAlgorithm = ShortestPathAlgorithmFactory.createAlgorithm(evt.getAlgorithmClass(), data); } catch (Exception e1) { JOptionPane.showMessageDialog(MainWindow.this, "An error occurred while creating the specified algorithm.", - "Internal error: Algorithm instantiation failure", - JOptionPane.ERROR_MESSAGE); + "Internal error: Algorithm instantiation failure", JOptionPane.ERROR_MESSAGE); e1.printStackTrace(); return; } @@ -341,14 +338,12 @@ public class MainWindow extends JFrame { // We need to draw MapView, we have to check if the file exists. File mfile = null; if (isMapView) { - String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map")) - + ".mapfg"; + String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map")) + ".mapfg"; mfile = new File(mfpath); if (!mfile.exists()) { if (JOptionPane.showConfirmDialog(this, "The associated mapsforge (.mapfg) file has not been found, do you want to specify it manually?", - "File not found", - JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) { + "File not found", JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) { JFileChooser chooser = new JFileChooser(mfile.getParentFile()); if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { mfile = chooser.getSelectedFile(); @@ -418,16 +413,15 @@ public class MainWindow extends JFrame { reader.addObserver(progressBar); try { graph = reader.read(); + System.out.flush(); } catch (Exception exception) { progressBar.setVisible(false); progressBar = null; - JOptionPane.showMessageDialog(MainWindow.this, - "Unable to read graph from the selected file."); + JOptionPane.showMessageDialog(MainWindow.this, "Unable to read graph from the selected file."); exception.printStackTrace(System.out); return; } - notifyNewGraphLoaded(); String info = graph.getMapId(); if (graph.getMapName() != null && !graph.getMapName().isEmpty()) { @@ -435,8 +429,11 @@ public class MainWindow extends JFrame { } info += ", " + graph.getNodes().size() + " nodes"; graphInfoPanel.setText(info); + drawGraph(); + notifyNewGraphLoaded(); + for (JMenuItem item: graphLockItems) { item.setEnabled(true); } @@ -453,8 +450,7 @@ public class MainWindow extends JFrame { @Override public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); - FileNameExtensionFilter filter = new FileNameExtensionFilter("Graph files", - "mapgr"); + FileNameExtensionFilter filter = new FileNameExtensionFilter("Graph files", "mapgr"); chooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); chooser.setFileFilter(filter); if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) { @@ -462,12 +458,11 @@ public class MainWindow extends JFrame { DataInputStream stream; try { - stream = new DataInputStream(new BufferedInputStream( - new FileInputStream(chooser.getSelectedFile()))); + stream = new DataInputStream( + new BufferedInputStream(new FileInputStream(chooser.getSelectedFile()))); } catch (IOException e1) { - JOptionPane.showMessageDialog(MainWindow.this, - "Cannot open the selected file."); + JOptionPane.showMessageDialog(MainWindow.this, "Cannot open the selected file."); return; } loadGraph(new BinaryGraphReaderInsa2018(stream)); @@ -475,33 +470,6 @@ public class MainWindow extends JFrame { } })); - JMenuItem openOldMapItem = new JMenuItem("Open Map (Old version)... "); - openOldMapItem.addActionListener(baf.createBlockingAction(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JFileChooser chooser = new JFileChooser(); - FileNameExtensionFilter filter = new FileNameExtensionFilter( - "Map & compressed map files", "map"); - chooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); - chooser.setFileFilter(filter); - if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) { - graphFilePath = chooser.getSelectedFile().getAbsolutePath(); - - DataInputStream stream; - try { - stream = new DataInputStream(new BufferedInputStream( - new FileInputStream(chooser.getSelectedFile()))); - } - catch (IOException e1) { - JOptionPane.showMessageDialog(MainWindow.this, - "Cannot open the selected file."); - return; - } - loadGraph(new BinaryGraphReaderInsa2016(stream)); - } - } - })); - // Open Path item... JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P); openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK)); @@ -510,19 +478,18 @@ public class MainWindow extends JFrame { @Override public void actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser(); - FileNameExtensionFilter filter = new FileNameExtensionFilter( - "Path & compressed path files", "path", "path.gz"); + FileNameExtensionFilter filter = new FileNameExtensionFilter("Path & compressed path files", "path", + "path.gz"); chooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); chooser.setFileFilter(filter); if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) { BinaryPathReader reader; try { - reader = new BinaryPathReader(new DataInputStream(new BufferedInputStream( - new FileInputStream(chooser.getSelectedFile())))); + reader = new BinaryPathReader(new DataInputStream( + new BufferedInputStream(new FileInputStream(chooser.getSelectedFile())))); } catch (IOException e1) { - JOptionPane.showMessageDialog(MainWindow.this, - "Cannot open the selected file."); + JOptionPane.showMessageDialog(MainWindow.this, "Cannot open the selected file."); return; } try { @@ -535,8 +502,7 @@ public class MainWindow extends JFrame { return; } catch (Exception exception) { - JOptionPane.showMessageDialog(MainWindow.this, - "Unable to read path from the selected file."); + JOptionPane.showMessageDialog(MainWindow.this, "Unable to read path from the selected file."); return; } } @@ -550,15 +516,13 @@ public class MainWindow extends JFrame { closeItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - MainWindow.this.dispatchEvent( - new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING)); + MainWindow.this.dispatchEvent(new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING)); } }); // Build the first menu. JMenu fileMenu = new JMenu("File"); fileMenu.add(openMapItem); - fileMenu.add(openOldMapItem); // TODO: Remove this for Students. fileMenu.add(openPathItem); fileMenu.addSeparator(); fileMenu.add(closeItem); @@ -593,8 +557,7 @@ public class MainWindow extends JFrame { })); graphLockItems.add(drawGraphBWItem); JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M); - drawGraphMapsforgeItem - .setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK)); + drawGraphMapsforgeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK)); drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -669,9 +632,8 @@ public class MainWindow extends JFrame { private JPanel createStatusBar() { // create the status bar panel and shove it down the bottom of the frame JPanel statusPanel = new JPanel(); - statusPanel.setBorder( - new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY), - new EmptyBorder(0, 15, 0, 15))); + statusPanel.setBorder(new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY), + new EmptyBorder(0, 15, 0, 15))); statusPanel.setPreferredSize(new Dimension(getWidth(), 38)); statusPanel.setLayout(new BorderLayout()); @@ -688,8 +650,8 @@ public class MainWindow extends JFrame { public void actionPerformed(ActionEvent e) { if (currentThread.isRunning()) { int confirmed = JOptionPane.showConfirmDialog(MainWindow.this, - "Are you sure you want to kill the running thread?", - "Kill Confirmation", JOptionPane.YES_NO_OPTION); + "Are you sure you want to kill the running thread?", "Kill Confirmation", + JOptionPane.YES_NO_OPTION); if (confirmed == JOptionPane.YES_OPTION) { currentThread.interrupt(); } @@ -701,8 +663,8 @@ public class MainWindow extends JFrame { @Override public void actionPerformed(ActionEvent e) { long seconds = currentThread.getDuration().getSeconds(); - threadTimerLabel.setText(String.format("%02d:%02d:%02d", seconds / 3600, - seconds / 60 % 60, seconds % 60)); + threadTimerLabel + .setText(String.format("%02d:%02d:%02d", seconds / 3600, seconds / 60 % 60, seconds % 60)); } }); threadTimer.setInitialDelay(0); diff --git a/src/main/org/insa/graphics/NodesInputPanel.java b/src/main/org/insa/graphics/NodesInputPanel.java index 83844ea..c9da8a5 100644 --- a/src/main/org/insa/graphics/NodesInputPanel.java +++ b/src/main/org/insa/graphics/NodesInputPanel.java @@ -34,10 +34,54 @@ public class NodesInputPanel extends JPanel /** * */ - private static final long serialVersionUID = -1638302070013027690L; + private static final long serialVersionUID = 1L; private static final Color DEFAULT_MARKER_COLOR = Color.BLUE; + /** + * Utility class that can be used to find a node from coordinates in a "fast" + * way. + * + */ + private static class NodeFinder { + + // Graph associated with this node finder. + private Graph graph; + + /** + * @param graph + */ + public NodeFinder(Graph graph) { + this.graph = graph; + } + + /** + * @param point + * + * @return the closest node to the given point, or null if no node is "close + * enough". + */ + public Node findClosestNode(Point point) { + Node minNode = null; + double minDis = Double.POSITIVE_INFINITY; + for (Node node: graph.getNodes()) { + double dlon = point.getLongitude() - node.getPoint().getLongitude(); + double dlat = point.getLatitude() - node.getPoint().getLatitude(); + double dis = dlon * dlon + dlat * dlat; // No need to square + if (dis < minDis) { + minNode = node; + minDis = dis; + } + } + return minNode; + } + + } + + /** + * Event data send when a node input has changed. + * + */ public class InputChangedEvent extends ActionEvent { /** @@ -77,6 +121,7 @@ public class NodesInputPanel extends JPanel // Drawing and graph private Drawing drawing; private Graph graph; + private NodeFinder nodeFinder; /** * @param drawing Original drawing used (see {@link:newDrawingLoaded}). @@ -316,7 +361,7 @@ public class NodesInputPanel extends JPanel public void mouseClicked(Point point) { JTextField input = getInputToFill(); if (input != null) { - Node node = graph.findClosestNode(point); + Node node = nodeFinder.findClosestNode(point); input.setText(String.valueOf(node.getId())); nextInputToFill(); } @@ -328,6 +373,8 @@ public class NodesInputPanel extends JPanel this.clear(); this.graph = graph; + nodeFinder = new NodeFinder(graph); + // Disable if previously disabled... setEnabled(this.isEnabled()); } diff --git a/src/main/org/insa/graphics/ShortestPathSolutionPanel.java b/src/main/org/insa/graphics/ShortestPathSolutionPanel.java index e507ab6..a90c134 100644 --- a/src/main/org/insa/graphics/ShortestPathSolutionPanel.java +++ b/src/main/org/insa/graphics/ShortestPathSolutionPanel.java @@ -32,8 +32,7 @@ import org.insa.graph.io.BinaryPathWriter; import org.insa.graphics.drawing.Drawing; import org.insa.graphics.drawing.overlays.PathOverlay; -public class ShortestPathSolutionPanel extends JPanel - implements DrawingChangeListener, GraphChangeListener { +public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener { /** * @@ -98,12 +97,13 @@ public class ShortestPathSolutionPanel extends JPanel /* * (non-Javadoc) + * * @see java.lang.Object#toString() */ public String toString() { return "Shortest-path from #" + this.getData().getOrigin().getId() + " to #" - + this.getData().getDestination().getId() + " [" - + this.getData().getMode().toString().toLowerCase() + "]"; + + this.getData().getDestination().getId() + " [" + this.getData().getMode().toString().toLowerCase() + + "]"; } } @@ -167,10 +167,9 @@ public class ShortestPathSolutionPanel extends JPanel @Override public void actionPerformed(ActionEvent e) { String filepath = System.getProperty("user.dir"); - filepath += File.separator + String.format("path_%#x_%d_%d.path", - currentBundle.getData().getGraph().getMapId(), - currentBundle.getData().getOrigin().getId(), - currentBundle.getData().getDestination().getId()); + filepath += File.separator + String.format("path_%s_%d_%d.path", + currentBundle.getData().getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", "_"), + currentBundle.getData().getOrigin().getId(), currentBundle.getData().getDestination().getId()); JFileChooser fileChooser = new JFileChooser(); fileChooser.setSelectedFile(new File(filepath)); fileChooser.setApproveButtonText("Save"); @@ -178,13 +177,12 @@ public class ShortestPathSolutionPanel extends JPanel if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); try { - BinaryPathWriter writer = new BinaryPathWriter(new DataOutputStream( - new BufferedOutputStream(new FileOutputStream(file)))); + BinaryPathWriter writer = new BinaryPathWriter( + new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))); writer.writePath(currentBundle.getSolution().getPath()); } catch (IOException e1) { - JOptionPane.showMessageDialog(parent, - "Unable to write path to the selected file."); + JOptionPane.showMessageDialog(parent, "Unable to write path to the selected file."); e1.printStackTrace(); } } @@ -237,8 +235,8 @@ public class ShortestPathSolutionPanel extends JPanel ShortestPathData data = bundle.getData(); String info = null; if (!bundle.getSolution().isFeasible()) { - info = String.format("No path found from node #%d to node #%d.", - data.getOrigin().getId(), data.getDestination().getId()); + info = String.format("No path found from node #%d to node #%d.", data.getOrigin().getId(), + data.getDestination().getId()); } else { info = String.format("Found a path from node #%d to node #%d", data.getOrigin().getId(), @@ -275,6 +273,12 @@ public class ShortestPathSolutionPanel extends JPanel @Override public void newGraphLoaded(Graph graph) { + for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) { + PathOverlay overlay = this.solutionSelect.getItemAt(i).getOverlay(); + if (overlay != null) { + overlay.delete(); + } + } this.solutionSelect.removeAllItems(); this.currentBundle = null; this.setVisible(false); diff --git a/src/main/org/insa/graphics/drawing/BasicDrawing.java b/src/main/org/insa/graphics/drawing/BasicDrawing.java index 3bd0dd6..580d9c0 100644 --- a/src/main/org/insa/graphics/drawing/BasicDrawing.java +++ b/src/main/org/insa/graphics/drawing/BasicDrawing.java @@ -8,7 +8,6 @@ import java.awt.Image; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; @@ -129,8 +128,7 @@ public class BasicDrawing extends JPanel implements Drawing { double scale = DEFAULT_MARKER_WIDTH / (double) img.getHeight(); gr.scale(scale, scale); - graphics.drawImage(img, px - img.getWidth() / 2, py - img.getHeight(), - BasicDrawing.this); + graphics.drawImage(img, px - img.getWidth() / 2, py - img.getHeight(), BasicDrawing.this); } }; @@ -279,8 +277,7 @@ public class BasicDrawing extends JPanel implements Drawing { private Graphics2D graphGraphics = null; // List of image for markers - private List overlays = Collections - .synchronizedList(new ArrayList()); + private List overlays = Collections.synchronizedList(new ArrayList()); // Mapping DrawingClickListener -> MouseEventListener private Map listenerMapping = new IdentityHashMap<>(); @@ -290,14 +287,7 @@ public class BasicDrawing extends JPanel implements Drawing { * */ public BasicDrawing() { - this.zoomAndPanListener = new ZoomAndPanListener(this, - ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2); - this.addMouseListener(zoomAndPanListener); - this.addMouseMotionListener(zoomAndPanListener); - this.addMouseWheelListener(zoomAndPanListener); - - // Avoid bunch of NullPointerException - this.zoomAndPanListener.setCoordTransform(new AffineTransform()); + this.zoomAndPanListener = new ZoomAndPanListener(this, ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2); } @Override @@ -347,8 +337,7 @@ public class BasicDrawing extends JPanel implements Drawing { // Get the point using the inverse transform of the Zoom/Pan object, this gives // us // a point within the drawing box (between [0, 0] and [width, height]). - Point2D ptDst = this.zoomAndPanListener.getCoordTransform() - .inverseTransform(event.getPoint(), null); + Point2D ptDst = this.zoomAndPanListener.getCoordTransform().inverseTransform(event.getPoint(), null); // Inverse the "projection" on x/y to get longitude and latitude. double lon = ptDst.getX(); @@ -427,9 +416,9 @@ public class BasicDrawing extends JPanel implements Drawing { * * @param arc Arc to draw. * @param palette Palette to use to retrieve color and width for arc, or null to - * use current settings. + * use current settings. */ - protected void drawArc(Arc arc, GraphPalette palette) { + protected void drawArc(Arc arc, GraphPalette palette, boolean repaint) { List pts = arc.getPoints(); if (!pts.isEmpty()) { if (palette != null) { @@ -450,7 +439,9 @@ public class BasicDrawing extends JPanel implements Drawing { prev = curr; } } - this.repaint(); + if (repaint) { + this.repaint(); + } } /** @@ -464,8 +455,8 @@ public class BasicDrawing extends JPanel implements Drawing { this.clear(); // Find minimum/maximum longitude and latitude. - double minLon = Double.POSITIVE_INFINITY, minLat = Double.POSITIVE_INFINITY, - maxLon = Double.NEGATIVE_INFINITY, maxLat = Double.NEGATIVE_INFINITY; + double minLon = Double.POSITIVE_INFINITY, minLat = Double.POSITIVE_INFINITY, maxLon = Double.NEGATIVE_INFINITY, + maxLat = Double.NEGATIVE_INFINITY; for (Node node: graph.getNodes()) { Point pt = node.getPoint(); if (pt.getLatitude() < minLat) { @@ -503,8 +494,7 @@ public class BasicDrawing extends JPanel implements Drawing { } // Create the image - BufferedImage img = new BufferedImage(this.width, this.height, - BufferedImage.TYPE_3BYTE_BGR); + BufferedImage img = new BufferedImage(this.width, this.height, BufferedImage.TYPE_3BYTE_BGR); this.graphImage = img; this.graphGraphics = img.createGraphics(); this.graphGraphics.setBackground(Color.WHITE); @@ -512,12 +502,10 @@ public class BasicDrawing extends JPanel implements Drawing { // Set the zoom and pan listener - double scale = 1 / Math.max(this.width / (double) this.getWidth(), - this.height / (double) this.getHeight()); + double scale = 1 / Math.max(this.width / (double) this.getWidth(), this.height / (double) this.getHeight()); this.zoomAndPanListener.setCoordTransform(this.graphGraphics.getTransform()); - this.zoomAndPanListener.getCoordTransform().translate( - (this.getWidth() - this.width * scale) / 2, + this.zoomAndPanListener.getCoordTransform().translate((this.getWidth() - this.width * scale) / 2, (this.getHeight() - this.height * scale) / 2); this.zoomAndPanListener.getCoordTransform().scale(scale, scale); this.zoomAndPanListener.setZoomLevel(0); @@ -528,15 +516,33 @@ public class BasicDrawing extends JPanel implements Drawing { @Override public void drawGraph(Graph graph, GraphPalette palette) { + int repaintModulo = graph.getNodes().size() / 100; + + // Initialize the buffered image + this.initialize(graph); + + // Remove zoom and pan listener + this.removeMouseListener(zoomAndPanListener); + this.removeMouseMotionListener(zoomAndPanListener); + this.removeMouseWheelListener(zoomAndPanListener); + for (Node node: graph.getNodes()) { for (Arc arc: node.getSuccessors()) { - if (arc.getRoadInformation().isOneWay() - || arc.getOrigin().compareTo(arc.getDestination()) < 0) { - drawArc(arc, palette); + if (arc.getRoadInformation().isOneWay() || arc.getOrigin().compareTo(arc.getDestination()) < 0) { + drawArc(arc, palette, false); } } + if (node.getId() % repaintModulo == 0) { + this.repaint(); + } } + this.repaint(); + + // Re-add zoom and pan listener + this.addMouseListener(zoomAndPanListener); + this.addMouseMotionListener(zoomAndPanListener); + this.addMouseWheelListener(zoomAndPanListener); } @Override