001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.data.osm.visitor.paint;
003    
004    import java.awt.Color;
005    import java.awt.Graphics2D;
006    import java.awt.Point;
007    import java.awt.geom.GeneralPath;
008    import java.awt.geom.Point2D;
009    import java.util.Iterator;
010    
011    import org.openstreetmap.josm.Main;
012    import org.openstreetmap.josm.data.osm.BBox;
013    import org.openstreetmap.josm.data.osm.DataSet;
014    import org.openstreetmap.josm.data.osm.Node;
015    import org.openstreetmap.josm.data.osm.Way;
016    import org.openstreetmap.josm.data.osm.WaySegment;
017    import org.openstreetmap.josm.gui.NavigatableComponent;
018    import org.openstreetmap.josm.tools.CheckParameterUtil;
019    
020    /**
021     * <p>Abstract common superclass for {@link Rendering} implementations.</p>
022     *
023     */
024    public abstract class AbstractMapRenderer implements Rendering {
025    
026        /** the graphics context to which the visitor renders OSM objects */
027        protected Graphics2D g;
028        /** the map viewport - provides projection and hit detection functionality */
029        protected NavigatableComponent nc;
030        
031        /** if true, the paint visitor shall render OSM objects such that they
032         * look inactive. Example: rendering of data in an inactive layer using light gray as color only. */
033        protected boolean isInactiveMode;
034        /** Color Preference for background */
035        protected Color backgroundColor;
036        /** Color Preference for inactive objects */
037        protected Color inactiveColor;
038        /** Color Preference for selected objects */
039        protected Color selectedColor;
040        /** Color Preference for nodes */
041        protected Color nodeColor;
042    
043        /** Color Preference for hightlighted objects */
044        protected Color highlightColor;
045        /** Preference: size of virtual nodes (0 displayes display) */
046        protected int virtualNodeSize;
047        /** Preference: minimum space (displayed way length) to display virtual nodes */
048        protected int virtualNodeSpace;
049    
050        /** Preference: minimum space (displayed way length) to display segment numbers */
051        protected int segmentNumberSpace;
052        
053        /**
054         * <p>Creates an abstract paint visitor</p>
055         * 
056         * @param g the graphics context. Must not be null.
057         * @param nc the map viewport. Must not be null.
058         * @param isInactiveMode if true, the paint visitor shall render OSM objects such that they
059         * look inactive. Example: rendering of data in an inactive layer using light gray as color only.
060         * @throws IllegalArgumentException thrown if {@code g} is null
061         * @throws IllegalArgumentException thrown if {@code nc} is null
062         */
063        public AbstractMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) throws IllegalArgumentException{
064            CheckParameterUtil.ensureParameterNotNull(g);
065            CheckParameterUtil.ensureParameterNotNull(nc);
066            this.g = g;
067            this.nc = nc;
068            this.isInactiveMode = isInactiveMode;
069        }
070        
071        /**
072         * Draw the node as small rectangle with the given color.
073         *
074         * @param n  The node to draw.
075         * @param color The color of the node.
076         */
077        public abstract void drawNode(Node n, Color color, int size, boolean fill);
078    
079        /**
080         * Draw an number of the order of the two consecutive nodes within the
081         * parents way
082         * 
083         * @param p1 First point of the way segment.
084         * @param p2 Second point of the way segment.
085         * @param orderNumber The number of the segment in the way.
086         */
087        protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
088            if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
089                String on = Integer.toString(orderNumber);
090                int strlen = on.length();
091                int x = (p1.x+p2.x)/2 - 4*strlen;
092                int y = (p1.y+p2.y)/2 + 4;
093    
094                if (virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace)) {
095                    y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
096                }
097    
098                g.setColor(backgroundColor);
099                g.fillRect(x-1, y-12, 8*strlen+1, 14);
100                g.setColor(clr);
101                g.drawString(on, x, y);
102            }
103        }
104        
105        /**
106         * Draws virtual nodes.
107         *
108         * @param data The data set being rendered.
109         * @param bbox The bounding box being displayed.
110         */
111        public void drawVirtualNodes(DataSet data, BBox bbox) {
112            if (virtualNodeSize == 0 || data == null || bbox == null)
113                return;
114            // print normal virtual nodes
115            GeneralPath path = new GeneralPath();
116            for (Way osm : data.searchWays(bbox)) {
117                if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
118                    visitVirtual(path, osm);
119                }
120            }
121            g.setColor(nodeColor);
122            g.draw(path);
123            try {
124                // print highlighted virtual nodes. Since only the color changes, simply
125                // drawing them over the existing ones works fine (at least in their current
126                // simple style)
127                path = new GeneralPath();
128                for (WaySegment wseg: data.getHighlightedVirtualNodes()) {
129                    if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
130                        visitVirtual(path, wseg.toWay());
131                    }
132                }
133                g.setColor(highlightColor);
134                g.draw(path);
135            } catch (ArrayIndexOutOfBoundsException e) {
136                // Silently ignore any ArrayIndexOutOfBoundsException that may be raised 
137                // if the way has changed while being rendered (fix #7979)
138                // TODO: proper solution ?
139                // Idea from bastiK: avoid the WaySegment class and add another data class with { Way way; Node firstNode, secondNode; int firstIdx; }.
140                // 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
141                // and report changes in a more controlled manner.
142            }
143        }
144        
145        /**
146         * Reads the color definitions from preferences. This function is <code>public</code>, so that
147         * color names in preferences can be displayed even without calling the wireframe display before.
148         */
149        public void getColors() {
150            this.backgroundColor = PaintColors.BACKGROUND.get();
151            this.inactiveColor = PaintColors.INACTIVE.get();
152            this.selectedColor = PaintColors.SELECTED.get();
153            this.nodeColor = PaintColors.NODE.get();
154            this.highlightColor = PaintColors.HIGHLIGHT.get();
155        }
156        
157        /**
158         * Reads all the settings from preferences. Calls the @{link #getColors}
159         * function.
160         *
161         * @param virtual <code>true</code> if virtual nodes are used
162         */
163        protected void getSettings(boolean virtual) {
164            this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
165            this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
166            this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
167            getColors();
168        }
169        
170        /**
171         * Checks if a way segemnt is large enough for additional information display.
172         *
173         * @param p1 First point of the way segment.
174         * @param p2 Second point of the way segment.
175         * @param space The free space to check against.
176         * @return <code>true</code> if segment is larger than required space
177         */
178        public static boolean isLargeSegment(Point2D p1, Point2D p2, int space) {
179            double xd = Math.abs(p1.getX()-p2.getX());
180            double yd = Math.abs(p1.getY()-p2.getY());
181            return (xd+yd > space);
182        }
183        
184        /**
185         * Checks if segment is visible in display.
186         *
187         * @param p1 First point of the way segment.
188         * @param p2 Second point of the way segment.
189         * @return <code>true</code> if segment is visible.
190         */
191        protected boolean isSegmentVisible(Point p1, Point p2) {
192            if ((p1.x < 0) && (p2.x < 0)) return false;
193            if ((p1.y < 0) && (p2.y < 0)) return false;
194            if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
195            if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
196            return true;
197        }
198        
199        /**
200         * Creates path for drawing virtual nodes for one way.
201         *
202         * @param path The path to append drawing to.
203         * @param w The ways to draw node for.
204         */
205        public void visitVirtual(GeneralPath path, Way w) {
206            Iterator<Node> it = w.getNodes().iterator();
207            if (it.hasNext()) {
208                Point lastP = nc.getPoint(it.next());
209                while (it.hasNext())
210                {
211                    Point p = nc.getPoint(it.next());
212                    if (isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
213                    {
214                        int x = (p.x+lastP.x)/2;
215                        int y = (p.y+lastP.y)/2;
216                        path.moveTo(x-virtualNodeSize, y);
217                        path.lineTo(x+virtualNodeSize, y);
218                        path.moveTo(x, y-virtualNodeSize);
219                        path.lineTo(x, y+virtualNodeSize);
220                    }
221                    lastP = p;
222                }
223            }
224        }
225    }