1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.gui;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
5 | import static org.openstreetmap.josm.tools.I18n.marktr;
|
---|
6 |
|
---|
7 | import java.awt.Color;
|
---|
8 | import java.awt.Dimension;
|
---|
9 | import java.awt.Graphics;
|
---|
10 | import java.awt.geom.Rectangle2D;
|
---|
11 |
|
---|
12 | import javax.accessibility.Accessible;
|
---|
13 | import javax.accessibility.AccessibleContext;
|
---|
14 | import javax.accessibility.AccessibleValue;
|
---|
15 | import javax.swing.JComponent;
|
---|
16 |
|
---|
17 | import org.openstreetmap.josm.data.preferences.NamedColorProperty;
|
---|
18 | import org.openstreetmap.josm.gui.help.Helpful;
|
---|
19 |
|
---|
20 | /**
|
---|
21 | * Map scale bar, displaying the distance in meter that correspond to 100 px on screen.
|
---|
22 | * @since 115
|
---|
23 | */
|
---|
24 | public class MapScaler extends JComponent implements Helpful, Accessible {
|
---|
25 |
|
---|
26 | private final NavigatableComponent mv;
|
---|
27 |
|
---|
28 | private static final int PADDING_LEFT = 5;
|
---|
29 | private static final int PADDING_RIGHT = 50;
|
---|
30 |
|
---|
31 | private static final NamedColorProperty SCALER_COLOR = new NamedColorProperty(marktr("scale"), Color.WHITE);
|
---|
32 |
|
---|
33 | /**
|
---|
34 | * Constructs a new {@code MapScaler}.
|
---|
35 | * @param mv map view
|
---|
36 | */
|
---|
37 | public MapScaler(NavigatableComponent mv) {
|
---|
38 | this.mv = mv;
|
---|
39 | setPreferredLineLength(100);
|
---|
40 | setOpaque(false);
|
---|
41 | }
|
---|
42 |
|
---|
43 | /**
|
---|
44 | * Sets the preferred length the distance line should have.
|
---|
45 | * @param pixel The length.
|
---|
46 | */
|
---|
47 | public void setPreferredLineLength(int pixel) {
|
---|
48 | setPreferredSize(new Dimension(pixel + PADDING_LEFT + PADDING_RIGHT, 30));
|
---|
49 | }
|
---|
50 |
|
---|
51 | @Override
|
---|
52 | public void paint(Graphics g) {
|
---|
53 | g.setColor(getColor());
|
---|
54 |
|
---|
55 | double dist100Pixel = mv.getDist100Pixel(true);
|
---|
56 | TickMarks tickMarks = new TickMarks(dist100Pixel, getWidth() - PADDING_LEFT - PADDING_RIGHT);
|
---|
57 | tickMarks.paintTicks(g);
|
---|
58 | }
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * Returns the color of map scaler.
|
---|
62 | * @return the color of map scaler
|
---|
63 | */
|
---|
64 | public static Color getColor() {
|
---|
65 | return SCALER_COLOR.get();
|
---|
66 | }
|
---|
67 |
|
---|
68 | @Override
|
---|
69 | public String helpTopic() {
|
---|
70 | return ht("/MapView/Scaler");
|
---|
71 | }
|
---|
72 |
|
---|
73 | @Override
|
---|
74 | public AccessibleContext getAccessibleContext() {
|
---|
75 | if (accessibleContext == null) {
|
---|
76 | accessibleContext = new AccessibleMapScaler();
|
---|
77 | }
|
---|
78 | return accessibleContext;
|
---|
79 | }
|
---|
80 |
|
---|
81 | class AccessibleMapScaler extends AccessibleJComponent implements AccessibleValue {
|
---|
82 |
|
---|
83 | @Override
|
---|
84 | public Number getCurrentAccessibleValue() {
|
---|
85 | return mv.getDist100Pixel();
|
---|
86 | }
|
---|
87 |
|
---|
88 | @Override
|
---|
89 | public boolean setCurrentAccessibleValue(Number n) {
|
---|
90 | return false;
|
---|
91 | }
|
---|
92 |
|
---|
93 | @Override
|
---|
94 | public Number getMinimumAccessibleValue() {
|
---|
95 | return null;
|
---|
96 | }
|
---|
97 |
|
---|
98 | @Override
|
---|
99 | public Number getMaximumAccessibleValue() {
|
---|
100 | return null;
|
---|
101 | }
|
---|
102 | }
|
---|
103 |
|
---|
104 | /**
|
---|
105 | * This class finds the best possible tick mark positions.
|
---|
106 | * <p>
|
---|
107 | * It will attempt to use steps of 1m, 2.5m, 10m, 25m, ...
|
---|
108 | */
|
---|
109 | private static final class TickMarks {
|
---|
110 |
|
---|
111 | private final double dist100Pixel;
|
---|
112 | /**
|
---|
113 | * Distance in meters between two ticks.
|
---|
114 | */
|
---|
115 | private final double spacingMeter;
|
---|
116 | private final int steps;
|
---|
117 | private final int minorStepsPerMajor;
|
---|
118 |
|
---|
119 | /**
|
---|
120 | * Creates a new tick mark helper.
|
---|
121 | * @param dist100Pixel The distance of 100 pixel on the map.
|
---|
122 | * @param width The width of the mark.
|
---|
123 | */
|
---|
124 | TickMarks(double dist100Pixel, int width) {
|
---|
125 | this.dist100Pixel = dist100Pixel;
|
---|
126 | double lineDistance = dist100Pixel * width / 100;
|
---|
127 |
|
---|
128 | double log10 = Math.log(lineDistance) / Math.log(10);
|
---|
129 | double spacingLog10 = Math.pow(10, Math.floor(log10));
|
---|
130 | int minorStepsPerMajor;
|
---|
131 | double distanceBetweenMinor;
|
---|
132 | if (log10 - Math.floor(log10) < .75) {
|
---|
133 | // Add 2 ticks for every full unit
|
---|
134 | distanceBetweenMinor = spacingLog10 / 2;
|
---|
135 | minorStepsPerMajor = 2;
|
---|
136 | } else {
|
---|
137 | // Add 10 ticks for every full unit
|
---|
138 | distanceBetweenMinor = spacingLog10;
|
---|
139 | minorStepsPerMajor = 5;
|
---|
140 | }
|
---|
141 | // round down to the last major step.
|
---|
142 | int majorSteps = (int) Math.floor(lineDistance / distanceBetweenMinor / minorStepsPerMajor);
|
---|
143 | if (majorSteps >= 4) {
|
---|
144 | // we have many major steps, do not paint the minor now.
|
---|
145 | this.spacingMeter = distanceBetweenMinor * minorStepsPerMajor;
|
---|
146 | this.minorStepsPerMajor = 1;
|
---|
147 | } else {
|
---|
148 | this.minorStepsPerMajor = minorStepsPerMajor;
|
---|
149 | this.spacingMeter = distanceBetweenMinor;
|
---|
150 | }
|
---|
151 | steps = majorSteps * this.minorStepsPerMajor;
|
---|
152 | }
|
---|
153 |
|
---|
154 | /**
|
---|
155 | * Paint the ticks to the graphics.
|
---|
156 | * @param g The graphics to paint on.
|
---|
157 | */
|
---|
158 | public void paintTicks(Graphics g) {
|
---|
159 | double spacingPixel = spacingMeter / (dist100Pixel / 100);
|
---|
160 | double textBlockedUntil = -1;
|
---|
161 | for (int step = 0; step <= steps; step++) {
|
---|
162 | int x = (int) (PADDING_LEFT + spacingPixel * step);
|
---|
163 | boolean isMajor = step % minorStepsPerMajor == 0;
|
---|
164 | int paddingY = isMajor ? 0 : 3;
|
---|
165 | g.drawLine(x, paddingY, x, 10 - paddingY);
|
---|
166 |
|
---|
167 | if (step == 0 || step == steps) {
|
---|
168 | String text;
|
---|
169 | if (step == 0) {
|
---|
170 | text = "0";
|
---|
171 | } else {
|
---|
172 | text = NavigatableComponent.getDistText(spacingMeter * step);
|
---|
173 | }
|
---|
174 | Rectangle2D bound = g.getFontMetrics().getStringBounds(text, g);
|
---|
175 | int left = (int) (x - bound.getWidth() / 2);
|
---|
176 | if (textBlockedUntil > left) {
|
---|
177 | left = (int) (textBlockedUntil + 5);
|
---|
178 | }
|
---|
179 | g.drawString(text, left, 23);
|
---|
180 | textBlockedUntil = left + bound.getWidth() + 2;
|
---|
181 | }
|
---|
182 | }
|
---|
183 | g.drawLine(PADDING_LEFT + 0, 5, (int) (PADDING_LEFT + spacingPixel * steps), 5);
|
---|
184 | }
|
---|
185 | }
|
---|
186 | }
|
---|