package edu.kit.pse.ws2013.routekit.mapdisplay; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.Iterator; import java.util.Set; import edu.kit.pse.ws2013.routekit.controllers.ProfileMapManager; import edu.kit.pse.ws2013.routekit.map.EdgeBasedGraph; import edu.kit.pse.ws2013.routekit.map.EdgeProperties; import edu.kit.pse.ws2013.routekit.map.Graph; import edu.kit.pse.ws2013.routekit.map.GraphIndex; import edu.kit.pse.ws2013.routekit.map.GraphView; import edu.kit.pse.ws2013.routekit.map.HighwayType; import edu.kit.pse.ws2013.routekit.map.IdentityGraphView; import edu.kit.pse.ws2013.routekit.models.ArcFlags; import edu.kit.pse.ws2013.routekit.models.ProfileMapCombination; import edu.kit.pse.ws2013.routekit.models.Weights; import edu.kit.pse.ws2013.routekit.util.Coordinates; /** * A {@link TileSource} that renders tiles itself. */ public class TileRenderer implements TileSource { private Graph graph; // 0=OFF, 1=ARC, 2=WEIGHT, 3=MAX_SPEED, 4=ID public static final int DEBUG_VIS = 0; class EdgeIterator { private int xstart; private int ystart; private int xtarget; private int ytarget; EdgeProperties p; int edge; Iterator<Integer> edges; private int zoom; private int x; private int y; private GraphView view; public EdgeIterator(Iterator<Integer> edges, int zoom, int x, int y, GraphIndex index) { this.edges = edges; this.zoom = zoom; this.x = x; this.y = y; this.view = index.getView(); } public boolean next() { int edg; do { if (!edges.hasNext()) { return false; } edg = edges.next(); } while (view instanceof IdentityGraphView && graph.getCorrespondingEdge(view.translate(edg)) > view .translate(edg)); extractCoordinates(edg); return true; } /** * Sets start and target coordinates */ private void extractCoordinates(int edgeV) { int startNode = view.getStartNode(edgeV); int targetNode = view.getTargetNode(edgeV); this.edge = view.translate(edgeV); Coordinates coordsTargetNode = graph.getCoordinates(targetNode); Coordinates coordsStartNode = graph.getCoordinates(startNode); xstart = (int) ((coordsStartNode.getSmtX(zoom) - x) * 256); ystart = (int) ((coordsStartNode.getSmtY(zoom) - y) * 256); xtarget = (int) ((coordsTargetNode.getSmtX(zoom) - x) * 256); ytarget = (int) ((coordsTargetNode.getSmtY(zoom) - y) * 256); p = graph.getEdgeProperties(edge); } } private static final int space = 10; private static final boolean DO_COLORFULL = true; /** * Creates a new {@link TileRenderer} for the given graph. * * @param graph * The {@link Graph} to render. */ public TileRenderer(final Graph graph) { this.graph = graph; } @Override public BufferedImage renderTile(final int x, final int y, final int zoom) { ProfileMapCombination debugCurrent; EdgeBasedGraph debugEbg; Weights debugWeights; ArcFlags debugAF; if (DEBUG_VIS != 0) { debugCurrent = ProfileMapManager.getInstance() .getCurrentCombination(); debugEbg = debugCurrent.getStreetMap().getEdgeBasedGraph(); debugWeights = debugCurrent.getWeights(); debugAF = debugCurrent.getArcFlags(); } GraphIndex index = graph.getIndex(zoom); final Set<Integer> edges = getEdgesOnTile(x, y, zoom, index); final BufferedImage tile = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); final Graphics2D g = tile.createGraphics(); g.setColor(new Color(210, 210, 210)); g.fillRect(0, 0, 256, 256); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); final Stroke oldStroke = g.getStroke(); // Draw border EdgeIterator it = new EdgeIterator(edges.iterator(), zoom, x, y, index); while (it.next()) { g.setColor(getMainStreetColor(it.p.getType(), true)); g.setStroke(new BasicStroke(getStreetWidth(zoom, it.p) + 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.drawLine(it.xstart, it.ystart, it.xtarget, it.ytarget); } // Draw street it = new EdgeIterator(edges.iterator(), zoom, x, y, index); while (it.next()) { g.setColor(getMainStreetColor(it.p.getType(), false)); g.setStroke(new BasicStroke(getStreetWidth(zoom, it.p), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g.drawLine(it.xstart, it.ystart, it.xtarget, it.ytarget); } g.setStroke(oldStroke); // Draw name of street – must be above all streets final Font font = new Font(Font.SANS_SERIF, 0, 12); g.setColor(Color.BLACK); g.setFont(font); it = new EdgeIterator(edges.iterator(), zoom, x, y, index); while (it.next()) { final String name = getName(it.edge); final double streetLength = Math.sqrt(Math.pow( (it.xtarget - it.xstart), 2) + Math.pow((it.ytarget - it.ystart), 2)); if (DEBUG_VIS != 0) { String debugData; int tturn = debugEbg.getOutgoingTurns(it.edge).iterator() .next(); if (DEBUG_VIS == 1) { debugData = Integer.toHexString(debugAF.getFlag(tturn)); } else if (DEBUG_VIS == 4) { debugData = Integer.toString(it.edge); } else {// 2,3 debugData = Integer.toString(DEBUG_VIS == 2 ? debugWeights .getWeight(tturn) : graph .getEdgeProperties(it.edge).getMaxSpeed( debugCurrent.getProfile())); } g.drawString(debugData, (it.xstart + it.xtarget) / 2, (it.ystart + it.ytarget) / 2); } if (name != null && (it.xstart != -1) && getStreetWidth(zoom, it.p) > 7) { final AffineTransform old = g.getTransform(); final Rectangle2D r = font.getStringBounds(name, g.getFontRenderContext()); double angle = getAngle(it.xstart, it.ystart, it.xtarget, it.ytarget, streetLength); // Pfeil <- if (zoom > 15 && streetLength > 40) { if (graph.getCorrespondingEdge(it.edge) == -1) { g.setColor(Color.LIGHT_GRAY); double angle2 = angle; if (it.xtarget < it.xstart || (it.xstart == it.xtarget && it.ystart > it.ytarget)) { angle2 -= Math.PI; } AffineTransform rotateInstance = AffineTransform .getRotateInstance(angle2, it.xstart, it.ystart); rotateInstance.translate(15 + it.xstart, it.ystart); g.setTransform(rotateInstance); drawArrow(zoom, g); g.setColor(Color.BLACK); } } int xstart; int ystart; if (it.xstart < it.xtarget || (it.xstart == it.xtarget && it.ystart < it.ytarget)) { xstart = it.xstart; ystart = it.ystart; } else { xstart = it.xtarget; ystart = it.ytarget; } final AffineTransform at = AffineTransform.getRotateInstance( angle, xstart, ystart); g.setTransform(at); if (r.getWidth() + 5 * space < streetLength) { g.drawString(name, (int) (xstart + streetLength / 2 - r .getWidth() / 2), (int) (ystart - r.getY() / 2 - r.getY() - r .getHeight()) + 1); } g.setTransform(old); } } return tile; } private void drawArrow(final int zoom, final Graphics2D g) { BasicStroke pen = new BasicStroke(2F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER); Polygon p = new Polygon(); double peakLength = 0.4; double tailThickness = 0.1; double length = 10 + (zoom - 15) * 10 / 4; double height = 4 + (zoom - 15) * 3 / 4; int ycenter = -(int) height / 2; p.addPoint((int) (length * peakLength), (int) (ycenter + (height - (height * tailThickness)) / 2)); p.addPoint((int) (length), (int) (ycenter + (height - (height * tailThickness)) / 2)); p.addPoint((int) (length), ycenter); p.addPoint((int) ((length) + (length * peakLength)), (int) height / 2 + ycenter); p.addPoint((int) (length), (int) height + ycenter); p.addPoint( (int) (length), (int) (ycenter + (height - (height * tailThickness)) / 2 + (height * tailThickness))); p.addPoint( (int) (length * peakLength), (int) (ycenter + (height - (height * tailThickness)) / 2 + (height * tailThickness))); g.setStroke(pen); g.fillPolygon(p); g.drawPolygon(p); } private Set<Integer> getEdgesOnTile(final int x, final int y, final int zoom, GraphIndex index) { final Coordinates leftTop = Coordinates.fromSmt(x - 0.1f, y + 1.1f, zoom); final Coordinates rightBottom = Coordinates.fromSmt(x + 1.1f, y - 0.1f, zoom); final Set<Integer> edges = index.getEdgesInRectangle(leftTop, rightBottom); return edges; } private Color getMainStreetColor(HighwayType type, boolean border) { Color col; if (DO_COLORFULL) { col = Color.getHSBColor( type.ordinal() / ((float) HighwayType.values().length), 0.2f, border ? 0.7f : 1f); } else { col = Color.BLACK; } return col; } private float getStreetWidth(int zoom, EdgeProperties p) { float width; if (p.getType() != HighwayType.Tertiary && p.getType() != HighwayType.Unclassified && p.getType() != HighwayType.Residential) { width = (HighwayType.values().length - p.getType().ordinal()) * 6 / (20f - zoom) + 2; } else { width = Math.max(28 - (20 - zoom) * 5, 1); } return width; } private String getName(final int edge) { final String name = graph.getEdgeProperties(edge).getName(); final String number = graph.getEdgeProperties(edge).getRoadRef(); if (name == null && number == null) { return null; } if (name != null) { return name; } else { return number; } } private double getAngle(final int xstart, final int ystart, final int xtarget, final int ytarget, final double checkValue) { if (xstart == xtarget) { return (Math.PI / 2d); } if (ystart == ytarget) { return 0d; } if ((ystart > ytarget && xstart < xtarget) || (ystart < ytarget && xtarget < xstart)) { return (-Math.asin((Math.abs(ytarget - ystart) / checkValue))); } if ((ystart < ytarget && xstart < xtarget) || (ytarget < ystart && xtarget < xstart)) { return Math.asin((Math.abs(ytarget - ystart)) / checkValue); } return -1; } }