// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm.visitor.paint;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import org.openstreetmap.josm.Main;
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.Way;
import org.openstreetmap.josm.data.osm.WaySegment;
import org.openstreetmap.josm.gui.MapViewState;
import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
import org.openstreetmap.josm.gui.NavigatableComponent;
import org.openstreetmap.josm.tools.CheckParameterUtil;
/**
* <p>Abstract common superclass for {@link Rendering} implementations.</p>
* @since 4087
*/
public abstract class AbstractMapRenderer implements Rendering {
/** the graphics context to which the visitor renders OSM objects */
protected final Graphics2D g;
/** the map viewport - provides projection and hit detection functionality */
protected final NavigatableComponent nc;
/**
* The {@link MapViewState} to use to convert between coordinates.
*/
protected final MapViewState mapState;
/** if true, the paint visitor shall render OSM objects such that they
* look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
protected boolean isInactiveMode;
/** Color Preference for background */
protected Color backgroundColor;
/** Color Preference for inactive objects */
protected Color inactiveColor;
/** Color Preference for selected objects */
protected Color selectedColor;
/** Color Preference for members of selected relations */
protected Color relationSelectedColor;
/** Color Preference for nodes */
protected Color nodeColor;
/** Color Preference for hightlighted objects */
protected Color highlightColor;
/** Preference: size of virtual nodes (0 displayes display) */
protected int virtualNodeSize;
/** Preference: minimum space (displayed way length) to display virtual nodes */
protected int virtualNodeSpace;
/** Preference: minimum space (displayed way length) to display segment numbers */
protected int segmentNumberSpace;
/**
* <p>Creates an abstract paint visitor</p>
*
* @param g the graphics context. Must not be null.
* @param nc the map viewport. Must not be null.
* @param isInactiveMode if true, the paint visitor shall render OSM objects such that they
* look inactive. Example: rendering of data in an inactive layer using light gray as color only.
* @throws IllegalArgumentException if {@code g} is null
* @throws IllegalArgumentException if {@code nc} is null
*/
public AbstractMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
CheckParameterUtil.ensureParameterNotNull(g);
CheckParameterUtil.ensureParameterNotNull(nc);
this.g = g;
this.nc = nc;
this.mapState = nc.getState();
this.isInactiveMode = isInactiveMode;
}
/**
* Draw the node as small square with the given color.
*
* @param n The node to draw.
* @param color The color of the node.
* @param size size in pixels
* @param fill determines if the square mmust be filled
*/
public abstract void drawNode(Node n, Color color, int size, boolean fill);
/**
* Draw an number of the order of the two consecutive nodes within the
* parents way
*
* @param p1 First point of the way segment.
* @param p2 Second point of the way segment.
* @param orderNumber The number of the segment in the way.
* @param clr The color to use for drawing the text.
* @since 10827
*/
protected void drawOrderNumber(MapViewPoint p1, MapViewPoint p2, int orderNumber, Color clr) {
if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
String on = Integer.toString(orderNumber);
int strlen = on.length();
double centerX = (p1.getInViewX()+p2.getInViewX())/2;
double centerY = (p1.getInViewY()+p2.getInViewY())/2;
double x = centerX - 4*strlen;
double y = centerY + 4;
if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
y = centerY - virtualNodeSize - 3;
}
g.setColor(backgroundColor);
g.fill(new Rectangle2D.Double(x-1, y-12, 8*strlen+1, 14));
g.setColor(clr);
g.drawString(on, (int) x, (int) y);
}
}
/**
* Draws virtual nodes.
*
* @param data The data set being rendered.
* @param bbox The bounding box being displayed.
*/
public void drawVirtualNodes(DataSet data, BBox bbox) {
if (virtualNodeSize == 0 || data == null || bbox == null)
return;
// print normal virtual nodes
GeneralPath path = new GeneralPath();
for (Way osm : data.searchWays(bbox)) {
if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
visitVirtual(path, osm);
}
}
g.setColor(nodeColor);
g.draw(path);
try {
// print highlighted virtual nodes. Since only the color changes, simply
// drawing them over the existing ones works fine (at least in their current simple style)
path = new GeneralPath();
for (WaySegment wseg: data.getHighlightedVirtualNodes()) {
if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
visitVirtual(path, wseg.toWay());
}
}
g.setColor(highlightColor);
g.draw(path);
} catch (ArrayIndexOutOfBoundsException e) {
// Silently ignore any ArrayIndexOutOfBoundsException that may be raised
// if the way has changed while being rendered (fix #7979)
// TODO: proper solution ?
// Idea from bastiK:
// avoid the WaySegment class and add another data class with { Way way; Node firstNode, secondNode; int firstIdx; }.
// On read, it would first check, if the way still has firstIdx+2 nodes, then check if the corresponding way nodes are still
// the same and report changes in a more controlled manner.
Main.trace(e);
}
}
/**
* Reads the color definitions from preferences. This function is <code>public</code>, so that
* color names in preferences can be displayed even without calling the wireframe display before.
*/
public void getColors() {
this.backgroundColor = PaintColors.BACKGROUND.get();
this.inactiveColor = PaintColors.INACTIVE.get();
this.selectedColor = PaintColors.SELECTED.get();
this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
this.nodeColor = PaintColors.NODE.get();
this.highlightColor = PaintColors.HIGHLIGHT.get();
}
/**
* Reads all the settings from preferences. Calls the @{link #getColors}
* function.
*
* @param virtual <code>true</code> if virtual nodes are used
*/
protected void getSettings(boolean virtual) {
this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
getColors();
}
/**
* Checks if a way segemnt is large enough for additional information display.
*
* @param p1 First point of the way segment.
* @param p2 Second point of the way segment.
* @param space The free space to check against.
* @return <code>true</code> if segment is larger than required space
* @since 10827
*/
public static boolean isLargeSegment(MapViewPoint p1, MapViewPoint p2, int space) {
return p1.oneNormInView(p2) > space;
}
/**
* Checks if segment is visible in display.
*
* @param p1 First point of the way segment.
* @param p2 Second point of the way segment.
* @return <code>true</code> if segment may be visible.
* @since 10827
*/
protected boolean isSegmentVisible(MapViewPoint p1, MapViewPoint p2) {
MapViewRectangle view = mapState.getViewArea();
// not outside in the same direction
return (p1.getOutsideRectangleFlags(view) & p2.getOutsideRectangleFlags(view)) == 0;
}
/**
* Creates path for drawing virtual nodes for one way.
*
* @param path The path to append drawing to.
* @param w The ways to draw node for.
* @since 10827
*/
public void visitVirtual(Path2D path, Way w) {
Iterator<Node> it = w.getNodes().iterator();
if (it.hasNext()) {
MapViewPoint lastP = mapState.getPointFor(it.next());
while (it.hasNext()) {
MapViewPoint p = mapState.getPointFor(it.next());
if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace)) {
double x = (p.getInViewX()+lastP.getInViewX())/2;
double y = (p.getInViewY()+lastP.getInViewY())/2;
path.moveTo(x-virtualNodeSize, y);
path.lineTo(x+virtualNodeSize, y);
path.moveTo(x, y-virtualNodeSize);
path.lineTo(x, y+virtualNodeSize);
}
lastP = p;
}
}
}
}