package edu.kit.pse.ws2013.routekit.views; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.Window; import java.awt.color.ColorSpace; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import javax.swing.JButton; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import edu.kit.pse.ws2013.routekit.controllers.MainController; import edu.kit.pse.ws2013.routekit.map.Graph; import edu.kit.pse.ws2013.routekit.mapdisplay.TileCache; import edu.kit.pse.ws2013.routekit.mapdisplay.TileFinishedListener; import edu.kit.pse.ws2013.routekit.mapdisplay.TileSource; import edu.kit.pse.ws2013.routekit.models.ProgressReporter; import edu.kit.pse.ws2013.routekit.models.RouteModel; import edu.kit.pse.ws2013.routekit.models.RouteModelListener; import edu.kit.pse.ws2013.routekit.routecalculation.Route; import edu.kit.pse.ws2013.routekit.util.Coordinates; /** * Displays a map section on the screen. * * As a map projection, the Mercator projection is used. */ public class MapView extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, TileFinishedListener, RouteModelListener, ActionListener { private static final long serialVersionUID = 1L; double x = 34297.855; double y = 22501.84; int zoom = 16; class ContextMenu extends JPopupMenu { private static final long serialVersionUID = 1L; JMenuItem start; JMenuItem target; public ContextMenu(final Coordinates coordinates) { start = new JMenuItem("Start hier"); add(start); start.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MainController.getInstance().setStartPoint(coordinates); } }); target = new JMenuItem("Ziel hier"); target.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MainController.getInstance().setDestinationPoint( coordinates); } }); add(target); } } TileSource source; private RouteModel rm; final BufferedImage flagPattern; /** * A constructor that creates a new MapView. The specified TileSource is * used for rendering. * * Because the tiles are requested synchronously with each MapView.paint(), * source should be a TileCache. * * @param source * An object that provides the map tiles, which are then * displayed. * @param rm * RouteModel to display */ public MapView(TileSource source, RouteModel rm) { this.rm = rm; rm.addRouteListener(this); this.source = source; if (source instanceof TileCache) { ((TileCache) source).addTileFinishedListener(this); } addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); in.addActionListener(this); out.addActionListener(this); in.setPreferredSize(new Dimension(45, 30)); out.setPreferredSize(new Dimension(45, 30)); add(in); add(out); add(startCalc); startCalc.setVisible(false); startCalc.setFont(new Font("Arial", Font.BOLD, 20)); startCalc.setBackground(Color.RED); startCalc.setForeground(Color.WHITE); startCalc.setSize(300, 200); startCalc.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { Container parentWindow = MapView.this.getParent(); while (!(parentWindow instanceof Window)) { parentWindow = parentWindow.getParent(); } ProgressDialog p = new ProgressDialog((Window) parentWindow, true); ProgressReporter reporter = new ProgressReporter(); reporter.addProgressListener(p); reporter.pushTask("Speichere Änderungen"); MainController.getInstance().startPrecalculation(reporter); p.setVisible(true); } }); flagPattern = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_BINARY); final Graphics g = flagPattern.getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, 5, 5); g.fillRect(5, 5, 5, 5); g.setColor(Color.black); g.fillRect(0, 5, 5, 5); g.fillRect(5, 0, 5, 5); } public void setTileSource(TileSource source) { this.source = source; if (source instanceof TileCache) { ((TileCache) source).addTileFinishedListener(this); } repaint(); } JButton startCalc = new JButton("<html>Vorberechnung starten</html>"); JButton in = new JButton("+"); JButton out = new JButton("-"); /** * Draws the currently visible map section. All visible tiles are requested * simultaneously from source. * * @param graphics * The Java Graphics, on which the map is drawn. */ @Override public void paint(Graphics graphics) { in.setLocation(getWidth() - in.getWidth() - 10, 10); out.setLocation(getWidth() - out.getWidth() - 10, 20 + in.getHeight()); graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, getWidth(), getHeight()); for (int i = (int) Math.floor(x); (i - x) * 256 < getWidth(); i++) { for (int j = (int) Math.floor(y); (j - y) * 256 < getHeight(); j++) { if (j < 0 || j >= 1 << zoom) { continue; } BufferedImage tile = source.renderTile(i & ((1 << zoom) - 1), j, zoom); if (!isEnabled()) { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); ColorConvertOp op = new ColorConvertOp(cs, null); tile = op.filter(tile, null); } graphics.drawImage(tile, (int) ((i - x) * 256), (int) ((j - y) * 256), null); } } if (!isEnabled()) { startCalc.setBounds((getWidth() - 300) / 2, (getHeight() - 200) / 2, 300, 200); super.paintComponents(graphics); return; } final Route r = rm.getCurrentRoute(); if (r != null) { drawRoute(graphics, r); } Coordinates c = rm.getStart(); if (c != null) { graphics.setColor(Color.RED); drawPoint(graphics, c, false); } c = rm.getDestination(); if (c != null) { graphics.setColor(Color.GREEN); drawPoint(graphics, c, true); } super.paintComponents(graphics); } private void drawRoute(Graphics g, final Route r) { g.setColor(new Color(255, 0, 0, 192)); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); Stroke s = g2.getStroke(); g2.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); int[] xPoints = new int[r.getTurns().size() + 2]; int[] yPoints = new int[xPoints.length]; int i = 0; for (Coordinates co : r) { xPoints[i] = (int) ((co.getSmtX(zoom) - x) * 256); yPoints[i] = (int) ((co.getSmtY(zoom) - y) * 256); i++; } g.drawPolyline(xPoints, yPoints, xPoints.length); Stroke stroke = new BasicStroke(6, // Width BasicStroke.CAP_ROUND, // End cap BasicStroke.JOIN_ROUND, // Join style 10.0f, // Miter limit new float[] { 5.0f, 10.0f }, // Dash pattern 0.0f); // Dash phase g2.setStroke(stroke); int startEdge = r.getStart().getEdge(); int destEdge = r.getDestination().getEdge(); Graph graph = r.getData().getStreetMap().getGraph(); Coordinates startOnEdge = graph.getCoordinates( graph.getStartNode(startEdge)).goIntoDirection( graph.getCoordinates(graph.getTargetNode(startEdge)), r.getStart().getPosition()); Coordinates destOnEdge = graph.getCoordinates( graph.getStartNode(destEdge)).goIntoDirection( graph.getCoordinates(graph.getTargetNode(destEdge)), r.getDestination().getPosition()); g2.drawLine((int) ((rm.getStart().getSmtX(zoom) - x) * 256), (int) ((rm .getStart().getSmtY(zoom) - y) * 256), (int) ((startOnEdge .getSmtX(zoom) - x) * 256), (int) ((startOnEdge.getSmtY(zoom) - y) * 256)); g2.drawLine((int) ((rm.getDestination().getSmtX(zoom) - x) * 256), (int) ((rm.getDestination().getSmtY(zoom) - y) * 256), (int) ((destOnEdge.getSmtX(zoom) - x) * 256), (int) ((destOnEdge.getSmtY(zoom) - y) * 256)); g2.setStroke(s); } private void drawPoint(Graphics g, Coordinates c, final boolean checkered) { int it = 1 << zoom; int smtX = (int) ((c.getSmtX(zoom) - x) * 256f); int smtY = (int) ((c.getSmtY(zoom) - y) * 256f); for (double i = Math.floor(x / it); (i * it - x) * 256 < getWidth(); i++) { int x = (int) (smtX + (i * it * 256)); int y = smtY; g.translate(x, y); if (checkered && g instanceof Graphics2D) { ((Graphics2D) g).setPaint(new TexturePaint(flagPattern, new Rectangle2D.Float(1, -2, 10, 10))); } drawFlag(g); g.translate(-x, -y); } } private static void drawFlag(Graphics g) { // @formatter:off // =================================== – // |------------flagWidth------------| | // | | | // | | | // | | | // | | | // | |-coneWidth-| | | // =========== =========== | flagHeight // \ / | | // \ / | | // \ / | | // \ / | coneHeight | // \ / | | // \ / | | // V – – // (0|0) // @formatter:on final int coneWidth = 8; final int coneHeight = 10; final int flagWidth = 20; final int flagHeight = 28; final int coneRadius = coneWidth / 2; final int halfFlagWidth = flagWidth / 2; Polygon p = new Polygon(new int[] { 0, -coneRadius, -halfFlagWidth, -halfFlagWidth, halfFlagWidth, halfFlagWidth, coneRadius }, new int[] { 0, -coneHeight, -coneHeight, -flagHeight, -flagHeight, -coneHeight, -coneHeight }, 7); g.fillPolygon(p); Color c = g.getColor(); g.setColor(Color.BLACK); g.drawPolygon(p); g.setColor(c); } int dx = -1; int dy = -1; int ozoom; double orgX; double orgY; private void applyDrag(MouseEvent e) { x = orgX - (e.getX() - dx) / 256f; y = orgY - (e.getY() - dy) / 256f; int limit = 1 << zoom; x = (x % limit + limit) % limit; zoom = ozoom; repaint(); } @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() % 2 == 0 && !e.isConsumed()) { e.consume(); mouseWheelMoved(new MouseWheelEvent(this, 0, System.currentTimeMillis(), 0, e.getX(), e.getY(), 2, false, 0, 1, -1)); } } @Override public void mousePressed(MouseEvent e) { if (!isEnabled()) { return; } if (e.isPopupTrigger()) { doPop(e); } else if (e.getButton() == MouseEvent.BUTTON1) { dx = e.getX(); dy = e.getY(); ozoom = zoom; orgX = x; orgY = y; } } @Override public void mouseReleased(MouseEvent e) { if (!isEnabled()) { return; } if (e.isPopupTrigger()) { doPop(e); } else if (e.getButton() == MouseEvent.BUTTON1) { applyDrag(e); } dx = -1; dy = -1; } private void doPop(MouseEvent e) { float y2 = (float) (y + e.getY() / 256f); if (y2 < 0 || y2 > 1 << zoom) { return; } Coordinates coordinates = Coordinates.fromSmt( (float) (x + e.getX() / 256f), y2, zoom); ContextMenu menu = new ContextMenu(coordinates); menu.show(e.getComponent(), e.getX(), e.getY()); } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseDragged(MouseEvent e) { if (!isEnabled()) { return; } if (dx == -1) { return; } applyDrag(e); } @Override public void mouseMoved(MouseEvent e) { } @Override public void mouseWheelMoved(MouseWheelEvent e) { if (!isEnabled() || dx != -1) { return; } int klick = e.getWheelRotation(); if (klick == 0) { return; } while (klick > 0 && zoom > 0) { klick--; zoom(e.getX(), e.getY(), true); } while (klick < 0 && zoom < 19) { klick++; zoom(e.getX(), e.getY(), false); } repaint(); } private void zoom(int xp, int yp, boolean out) { double yZ = y + yp / 256f; double xZ = x + xp / 256f; if (out) { yZ /= 2; xZ /= 2; zoom--; } else { yZ *= 2; xZ *= 2; zoom++; } y = yZ - yp / 256f; x = xZ - xp / 256f; int limit = 1 << zoom; x = (x % limit + limit) % limit; } @Override public void tileFinished(int x, int y, int zoom, BufferedImage tile) { repaint(); } @Override public void routeModelChanged() { repaint(); } @Override public void actionPerformed(ActionEvent e) { if (!isEnabled()) { return; } if (e.getSource() == in && zoom < 19) { zoom(getWidth() / 2, getHeight() / 2, false); repaint(); } else if (e.getSource() == out && zoom > 0) { zoom(getWidth() / 2, getHeight() / 2, true); repaint(); } } @Override public void setEnabled(boolean enabled) { in.setEnabled(enabled); out.setEnabled(enabled); startCalc.setVisible(!enabled); super.setEnabled(enabled); } public void setMapLocation(Coordinates firstNode, Coordinates lastNode) { zoom = 14; int width = getWidth(); if (width == 0) { width = 712; } int height = getHeight(); if (height == 0) { height = 600; } x = (firstNode.getSmtX(zoom) + lastNode.getSmtX(zoom)) / 2d - width / 512d; y = (firstNode.getSmtY(zoom) + lastNode.getSmtY(zoom)) / 2d - height / 512d; } }