/* License: GPL. Copyright 2007 by Immanuel Scholz and others */
package org.openstreetmap.josm.data.osm.visitor.paint;
/* To enable debugging or profiling remove the double / signs */
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.util.Collection;
import java.util.Iterator;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
import org.openstreetmap.josm.gui.NavigatableComponent;
/**
* A visitor that paints a simple scheme of every primitive it visits to a
* previous set graphic environment.
*
* @author imi
*/
public class SimplePaintVisitor extends AbstractVisitor implements PaintVisitor {
/**
* The environment to paint to.
*/
protected Graphics2D g;
/**
* MapView to get screen coordinates.
*/
protected NavigatableComponent nc;
public boolean inactive;
protected static final double PHI = Math.toRadians(20);
/**
* Preferences
*/
protected Color inactiveColor;
protected Color selectedColor;
protected Color nodeColor;
protected Color dfltWayColor;
protected Color relationColor;
protected Color untaggedWayColor;
protected Color incompleteColor;
protected Color backgroundColor;
protected Color highlightColor;
protected Color taggedColor;
protected Color connectionColor;
protected Color taggedConnectionColor;
protected boolean showDirectionArrow;
protected boolean showRelevantDirectionsOnly;
protected boolean showHeadArrowOnly;
protected boolean showOrderNumber;
protected boolean fillSelectedNode;
protected boolean fillUnselectedNode;
protected boolean fillTaggedNode;
protected boolean fillConnectionNode;
protected int selectedNodeSize;
protected int unselectedNodeSize;
protected int connectionNodeSize;
protected int taggedNodeSize;
protected int defaultSegmentWidth;
protected int virtualNodeSize;
protected int virtualNodeSpace;
protected int segmentNumberSpace;
/**
* Draw subsequent segments of same color as one Path
*/
protected Color currentColor = null;
protected GeneralPath currentPath = new GeneralPath();
public void getColors()
{
inactiveColor = PaintColors.INACTIVE.get();
selectedColor = PaintColors.SELECTED.get();
nodeColor = PaintColors.NODE.get();
dfltWayColor = PaintColors.DEFAULT_WAY.get();
relationColor = PaintColors.RELATION.get();
untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
incompleteColor = PaintColors.INCOMPLETE_WAY.get();
backgroundColor = PaintColors.BACKGROUND.get();
highlightColor = PaintColors.HIGHLIGHT.get();
taggedColor = PaintColors.TAGGED.get();
connectionColor = PaintColors.CONNECTION.get();
if (taggedColor != nodeColor) {
taggedConnectionColor = taggedColor;
} else {
taggedConnectionColor = connectionColor;
}
}
protected void getSettings(boolean virtual) {
MapPaintSettings settings = MapPaintSettings.INSTANCE;
showDirectionArrow = settings.isShowDirectionArrow();
showRelevantDirectionsOnly = settings.isShowRelevantDirectionsOnly();
showHeadArrowOnly = settings.isShowHeadArrowOnly();
showOrderNumber = settings.isShowOrderNumber();
selectedNodeSize = settings.getSelectedNodeSize();
unselectedNodeSize = settings.getUnselectedNodeSize();
connectionNodeSize = settings.getConnectionNodeSize();
taggedNodeSize = settings.getTaggedNodeSize();
defaultSegmentWidth = settings.getDefaultSegmentWidth();
fillSelectedNode = settings.isFillSelectedNode();
fillUnselectedNode = settings.isFillUnselectedNode();
fillConnectionNode = settings.isFillConnectionNode();
fillTaggedNode = settings.isFillTaggedNode();
virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
getColors();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
Main.pref.getBoolean("mappaint.use-antialiasing", false) ?
RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
}
DataSet ds;
public void visitAll(DataSet data, boolean virtual, Bounds bounds) {
BBox bbox = new BBox(bounds);
this.ds = data;
//boolean profiler = Main.pref.getBoolean("simplepaint.profiler",false);
//long profilerStart = java.lang.System.currentTimeMillis();
//long profilerLast = profilerStart;
//int profilerN = 0;
//if(profiler)
// System.out.println("Simplepaint Profiler");
getSettings(virtual);
//if(profiler)
//{
// System.out.format("Prepare : %4dms\n", (java.lang.System.currentTimeMillis()-profilerLast));
// profilerLast = java.lang.System.currentTimeMillis();
//}
/* draw tagged ways first, then untagged ways. takes
time to iterate through list twice, OTOH does not
require changing the colour while painting... */
//profilerN = 0;
for (final OsmPrimitive osm: data.searchRelations(bbox)) {
if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isFiltered()) {
osm.visit(this);
// profilerN++;
}
}
//if(profiler)
//{
// System.out.format("Relations: %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
// profilerLast = java.lang.System.currentTimeMillis();
//}
//profilerN = 0;
for (final OsmPrimitive osm:data.searchWays(bbox)){
if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isFiltered() && osm.isTagged()) {
osm.visit(this);
// profilerN++;
}
}
displaySegments();
for (final OsmPrimitive osm:data.searchWays(bbox)){
if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isFiltered() && !osm.isTagged()) {
osm.visit(this);
// profilerN++;
}
}
displaySegments();
//if(profiler)
//{
// System.out.format("Ways : %4dms, n=%5d\n",
// (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
// profilerLast = java.lang.System.currentTimeMillis();
//}
//profilerN = 0;
for (final OsmPrimitive osm : data.getSelected())
if (!osm.isDeleted()) {
osm.visit(this);
// profilerN++;
}
displaySegments();
//if(profiler)
//{
// System.out.format("Selected : %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
// profilerLast = java.lang.System.currentTimeMillis();
//}
//profilerN = 0;
for (final OsmPrimitive osm: data.searchNodes(bbox)) {
if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isFiltered())
{
osm.visit(this);
// profilerN++;
}
}
//if(profiler)
//{
// System.out.format("Nodes : %4dms, n=%5d\n",
// (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
// profilerLast = java.lang.System.currentTimeMillis();
//}
drawVirtualNodes(data.searchWays(bbox));
//if(profiler)
//{
// System.out.format("All : %4dms\n", (profilerLast-profilerStart));
//}
}
private static final int max(int a, int b, int c, int d) {
return Math.max(Math.max(a, b), Math.max(c, d));
}
/**
* Draw a small rectangle.
* White if selected (as always) or red otherwise.
*
* @param n The node to draw.
*/
public void visit(Node n) {
if (n.isIncomplete()) return;
if (n.isHighlighted()) {
drawNode(n, highlightColor, selectedNodeSize, fillSelectedNode);
} else {
Color color;
if (inactive || n.isDisabled()) {
color = inactiveColor;
} else if (ds.isSelected(n)) {
color = selectedColor;
} else if (n.isConnectionNode()) {
if (n.isTagged()) {
color = taggedConnectionColor;
} else {
color = connectionColor;
}
} else {
if (n.isTagged()) {
color = taggedColor;
} else {
color = nodeColor;
}
}
final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
(n.isTagged() ? taggedNodeSize : 0),
(n.isConnectionNode() ? connectionNodeSize : 0),
unselectedNodeSize);
final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
(n.isTagged() && fillTaggedNode) ||
(n.isConnectionNode() && fillConnectionNode) ||
fillUnselectedNode;
drawNode(n, color, size, fill);
}
}
public static boolean isLargeSegment(Point p1, Point p2, int space)
{
int xd = p1.x-p2.x; if(xd < 0) {
xd = -xd;
}
int yd = p1.y-p2.y; if(yd < 0) {
yd = -yd;
}
return (xd+yd > space);
}
public void drawVirtualNodes(Collection<Way> ways) {
if (virtualNodeSize != 0) {
GeneralPath path = new GeneralPath();
for (Way osm: ways){
if (osm.isUsable() && !osm.isFiltered() && !osm.isDisabled()) {
visitVirtual(path, osm);
}
}
g.setColor(nodeColor);
g.draw(path);
}
}
public void visitVirtual(GeneralPath path, Way w) {
Iterator<Node> it = w.getNodes().iterator();
if (it.hasNext()) {
Point lastP = nc.getPoint(it.next());
while(it.hasNext())
{
Point p = nc.getPoint(it.next());
if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
{
int x = (p.x+lastP.x)/2;
int y = (p.y+lastP.y)/2;
path.moveTo(x-virtualNodeSize, y);
path.lineTo(x+virtualNodeSize, y);
path.moveTo(x, y-virtualNodeSize);
path.lineTo(x, y+virtualNodeSize);
}
lastP = p;
}
}
}
/**
* Draw a darkblue line for all segments.
* @param w The way to draw.
*/
public void visit(Way w) {
if (w.isIncomplete() || w.getNodesCount() < 2)
return;
/* show direction arrows, if draw.segment.relevant_directions_only is not set, the way is tagged with a direction key
(even if the tag is negated as in oneway=false) or the way is selected */
boolean showThisDirectionArrow = ds.isSelected(w)
|| (showDirectionArrow && (!showRelevantDirectionsOnly || w.hasDirectionKeys()));
/* head only takes over control if the option is true,
the direction should be shown at all and not only because it's selected */
boolean showOnlyHeadArrowOnly = showThisDirectionArrow && !ds.isSelected(w) && showHeadArrowOnly;
Color wayColor;
if (inactive || w.isDisabled()) {
wayColor = inactiveColor;
} else if(w.isHighlighted()) {
wayColor = highlightColor;
} else if(ds.isSelected(w)) {
wayColor = selectedColor;
} else if (!w.isTagged()) {
wayColor = untaggedWayColor;
} else {
wayColor = dfltWayColor;
}
Iterator<Node> it = w.getNodes().iterator();
if (it.hasNext()) {
Point lastP = nc.getPoint(it.next());
for (int orderNumber = 1; it.hasNext(); orderNumber++) {
Point p = nc.getPoint(it.next());
drawSegment(lastP, p, wayColor,
showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
if (showOrderNumber) {
drawOrderNumber(lastP, p, orderNumber);
}
lastP = p;
}
}
}
private Stroke relatedWayStroke = new BasicStroke(
4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
public void visit(Relation r) {
if (r.isIncomplete()) return;
Color col;
if (inactive || r.isDisabled()) {
col = inactiveColor;
} else if (ds.isSelected(r)) {
col = selectedColor;
} else {
col = relationColor;
}
g.setColor(col);
for (RelationMember m : r.getMembers()) {
if (m.getMember().isIncomplete() || m.getMember().isDeleted()) {
continue;
}
if (m.isNode()) {
Point p = nc.getPoint(m.getNode());
if (p.x < 0 || p.y < 0
|| p.x > nc.getWidth() || p.y > nc.getHeight()) {
continue;
}
g.drawOval(p.x-3, p.y-3, 6, 6);
} else if (m.isWay()) {
GeneralPath path = new GeneralPath();
boolean first = true;
for (Node n : m.getWay().getNodes()) {
if (n.isIncomplete() || n.isDeleted()) {
continue;
}
Point p = nc.getPoint(n);
if (first) {
path.moveTo(p.x, p.y);
first = false;
} else {
path.lineTo(p.x, p.y);
}
}
g.draw(relatedWayStroke.createStrokedShape(path));
}
}
}
/**
* Draw an number of the order of the two consecutive nodes within the
* parents way
*/
protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
String on = Integer.toString(orderNumber);
int strlen = on.length();
int x = (p1.x+p2.x)/2 - 4*strlen;
int y = (p1.y+p2.y)/2 + 4;
if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
{
y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
}
displaySegments(); /* draw nodes on top! */
Color c = g.getColor();
g.setColor(backgroundColor);
g.fillRect(x-1, y-12, 8*strlen+1, 14);
g.setColor(c);
g.drawString(on, x, y);
}
}
/**
* Draw the node as small rectangle with the given color.
*
* @param n The node to draw.
* @param color The color of the node.
*/
public void drawNode(Node n, Color color, int size, boolean fill) {
if (size > 1) {
int radius = size / 2;
Point p = nc.getPoint(n);
if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
|| (p.y > nc.getHeight()))
return;
g.setColor(color);
if (fill) {
g.fillRect(p.x - radius, p.y - radius, size, size);
g.drawRect(p.x - radius, p.y - radius, size, size);
} else {
g.drawRect(p.x - radius, p.y - radius, size, size);
}
}
}
protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
if (isSegmentVisible(p1, p2)) {
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
if (showDirection) {
double t = Math.atan2(p2.y-p1.y, p2.x-p1.x) + Math.PI;
path.lineTo((int)(p2.x + 10*Math.cos(t-PHI)), (int)(p2.y + 10*Math.sin(t-PHI)));
path.moveTo((int)(p2.x + 10*Math.cos(t+PHI)), (int)(p2.y + 10*Math.sin(t+PHI)));
path.lineTo(p2.x, p2.y);
}
}
}
/**
* Draw a line with the given color.
*/
protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
if (col != currentColor) {
displaySegments(col);
}
drawSegment(currentPath, p1, p2, showDirection);
}
protected boolean isSegmentVisible(Point p1, Point p2) {
if ((p1.x < 0) && (p2.x < 0)) return false;
if ((p1.y < 0) && (p2.y < 0)) return false;
if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
return true;
}
protected boolean isPolygonVisible(Polygon polygon) {
Rectangle bounds = polygon.getBounds();
if (bounds.width == 0 && bounds.height == 0) return false;
if (bounds.x > nc.getWidth()) return false;
if (bounds.y > nc.getHeight()) return false;
if (bounds.x + bounds.width < 0) return false;
if (bounds.y + bounds.height < 0) return false;
return true;
}
public void setGraphics(Graphics2D g) {
this.g = g;
}
public void setNavigatableComponent(NavigatableComponent nc) {
this.nc = nc;
}
protected void displaySegments() {
displaySegments(null);
}
protected void displaySegments(Color newColor) {
if (currentPath != null) {
g.setColor(currentColor);
g.draw(currentPath);
currentPath = new GeneralPath();
currentColor = newColor;
}
}
public void setInactive(boolean inactive) {
this.inactive = inactive;
}
}