source: josm/trunk/src/org/openstreetmap/josm/gui/bbox/TileSelectionBBoxChooser.java@ 17318

Last change on this file since 17318 was 15145, checked in by Don-vip, 5 years ago

see #17772 - make sure all JMapViewer instances in JOSM behave the same

  • Property svn:eol-style set to native
File size: 24.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.bbox;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTKeyStroke;
7import java.awt.BorderLayout;
8import java.awt.Color;
9import java.awt.FlowLayout;
10import java.awt.Graphics;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Insets;
14import java.awt.KeyboardFocusManager;
15import java.awt.Point;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.awt.event.FocusEvent;
19import java.awt.event.FocusListener;
20import java.awt.event.KeyEvent;
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
23import java.util.ArrayList;
24import java.util.HashSet;
25import java.util.List;
26import java.util.Set;
27import java.util.regex.Matcher;
28import java.util.regex.Pattern;
29
30import javax.swing.AbstractAction;
31import javax.swing.BorderFactory;
32import javax.swing.JButton;
33import javax.swing.JLabel;
34import javax.swing.JPanel;
35import javax.swing.JSpinner;
36import javax.swing.KeyStroke;
37import javax.swing.SpinnerNumberModel;
38import javax.swing.event.ChangeEvent;
39import javax.swing.event.ChangeListener;
40import javax.swing.text.JTextComponent;
41
42import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
43import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
44import org.openstreetmap.josm.data.Bounds;
45import org.openstreetmap.josm.data.coor.LatLon;
46import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
47import org.openstreetmap.josm.gui.widgets.HtmlPanel;
48import org.openstreetmap.josm.gui.widgets.JosmTextField;
49import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.Utils;
52
53/**
54 * TileSelectionBBoxChooser allows to select a bounding box (i.e. for downloading) based
55 * on OSM tile numbers.
56 *
57 * TileSelectionBBoxChooser can be embedded as component in a Swing container. Example:
58 * <pre>
59 * JFrame f = new JFrame(....);
60 * f.getContentPane().setLayout(new BorderLayout()));
61 * TileSelectionBBoxChooser chooser = new TileSelectionBBoxChooser();
62 * f.add(chooser, BorderLayout.CENTER);
63 * chooser.addPropertyChangeListener(new PropertyChangeListener() {
64 * public void propertyChange(PropertyChangeEvent evt) {
65 * // listen for BBOX events
66 * if (evt.getPropertyName().equals(BBoxChooser.BBOX_PROP)) {
67 * Logging.info("new bbox based on OSM tiles selected: " + (Bounds)evt.getNewValue());
68 * }
69 * }
70 * });
71 *
72 * // init the chooser with a bounding box
73 * chooser.setBoundingBox(....);
74 *
75 * f.setVisible(true);
76 * </pre>
77 */
78public class TileSelectionBBoxChooser extends JPanel implements BBoxChooser {
79
80 /** the current bounding box */
81 private transient Bounds bbox;
82 /** the map viewer showing the selected bounding box */
83 private final TileBoundsMapView mapViewer = new TileBoundsMapView();
84 /** a panel for entering a bounding box given by a tile grid and a zoom level */
85 private final TileGridInputPanel pnlTileGrid = new TileGridInputPanel();
86 /** a panel for entering a bounding box given by the address of an individual OSM tile at a given zoom level */
87 private final TileAddressInputPanel pnlTileAddress = new TileAddressInputPanel();
88
89 /**
90 * builds the UI
91 */
92 protected final void build() {
93 setLayout(new GridBagLayout());
94
95 GridBagConstraints gc = new GridBagConstraints();
96 gc.weightx = 0.5;
97 gc.fill = GridBagConstraints.HORIZONTAL;
98 gc.anchor = GridBagConstraints.NORTHWEST;
99 add(pnlTileGrid, gc);
100
101 gc.gridx = 1;
102 add(pnlTileAddress, gc);
103
104 gc.gridx = 0;
105 gc.gridy = 1;
106 gc.gridwidth = 2;
107 gc.weightx = 1.0;
108 gc.weighty = 1.0;
109 gc.fill = GridBagConstraints.BOTH;
110 gc.insets = new Insets(2, 2, 2, 2);
111 add(mapViewer, gc);
112 mapViewer.setFocusable(false);
113 mapViewer.setZoomControlsVisible(false);
114 mapViewer.setMapMarkerVisible(false);
115
116 pnlTileAddress.addPropertyChangeListener(pnlTileGrid);
117 pnlTileGrid.addPropertyChangeListener(new TileBoundsChangeListener());
118 }
119
120 /**
121 * Constructs a new {@code TileSelectionBBoxChooser}.
122 */
123 public TileSelectionBBoxChooser() {
124 build();
125 }
126
127 /**
128 * Replies the current bounding box. null, if no valid bounding box is currently selected.
129 *
130 */
131 @Override
132 public Bounds getBoundingBox() {
133 return bbox;
134 }
135
136 /**
137 * Sets the current bounding box.
138 *
139 * @param bbox the bounding box. null, if this widget isn't initialized with a bounding box
140 */
141 @Override
142 public void setBoundingBox(Bounds bbox) {
143 pnlTileGrid.initFromBoundingBox(bbox);
144 }
145
146 protected void refreshMapView() {
147 if (bbox == null) return;
148
149 // calc the screen coordinates for the new selection rectangle
150 List<MapMarker> marker = new ArrayList<>(2);
151 marker.add(new MapMarkerDot(bbox.getMinLat(), bbox.getMinLon()));
152 marker.add(new MapMarkerDot(bbox.getMaxLat(), bbox.getMaxLon()));
153 mapViewer.setBoundingBox(bbox);
154 mapViewer.setMapMarkerList(marker);
155 mapViewer.setDisplayToFitMapMarkers();
156 mapViewer.zoomOut();
157 }
158
159 /**
160 * Computes the bounding box given a tile grid.
161 *
162 * @param tb the description of the tile grid
163 * @return the bounding box
164 */
165 protected Bounds convertTileBoundsToBoundingBox(TileBounds tb) {
166 LatLon min = getNorthWestLatLonOfTile(tb.min, tb.zoomLevel);
167 Point p = new Point(tb.max);
168 p.x++;
169 p.y++;
170 LatLon max = getNorthWestLatLonOfTile(p, tb.zoomLevel);
171 return new Bounds(max.lat(), min.lon(), min.lat(), max.lon());
172 }
173
174 /**
175 * Replies lat/lon of the north/west-corner of a tile at a specific zoom level
176 *
177 * @param tile the tile address (x,y)
178 * @param zoom the zoom level
179 * @return lat/lon of the north/west-corner of a tile at a specific zoom level
180 */
181 protected LatLon getNorthWestLatLonOfTile(Point tile, int zoom) {
182 double lon = tile.x / Math.pow(2.0, zoom) * 360.0 - 180;
183 double lat = Utils.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * tile.y) / Math.pow(2.0, zoom))));
184 return new LatLon(lat, lon);
185 }
186
187 /**
188 * Listens to changes in the selected tile bounds, refreshes the map view and emits
189 * property change events for {@link BBoxChooser#BBOX_PROP}
190 */
191 class TileBoundsChangeListener implements PropertyChangeListener {
192 @Override
193 public void propertyChange(PropertyChangeEvent evt) {
194 if (!evt.getPropertyName().equals(TileGridInputPanel.TILE_BOUNDS_PROP)) return;
195 TileBounds tb = (TileBounds) evt.getNewValue();
196 Bounds oldValue = TileSelectionBBoxChooser.this.bbox;
197 TileSelectionBBoxChooser.this.bbox = convertTileBoundsToBoundingBox(tb);
198 firePropertyChange(BBOX_PROP, oldValue, TileSelectionBBoxChooser.this.bbox);
199 refreshMapView();
200 }
201 }
202
203 /**
204 * A panel for describing a rectangular area of OSM tiles at a given zoom level.
205 *
206 * The panel emits PropertyChangeEvents for the property {@link TileGridInputPanel#TILE_BOUNDS_PROP}
207 * when the user successfully enters a valid tile grid specification.
208 *
209 */
210 private static class TileGridInputPanel extends JPanel implements PropertyChangeListener {
211 public static final String TILE_BOUNDS_PROP = TileGridInputPanel.class.getName() + ".tileBounds";
212
213 private final JosmTextField tfMaxY = new JosmTextField();
214 private final JosmTextField tfMinY = new JosmTextField();
215 private final JosmTextField tfMaxX = new JosmTextField();
216 private final JosmTextField tfMinX = new JosmTextField();
217 private transient TileCoordinateValidator valMaxY;
218 private transient TileCoordinateValidator valMinY;
219 private transient TileCoordinateValidator valMaxX;
220 private transient TileCoordinateValidator valMinX;
221 private final JSpinner spZoomLevel = new JSpinner(new SpinnerNumberModel(0, 0, 18, 1));
222 private final transient TileBoundsBuilder tileBoundsBuilder = new TileBoundsBuilder();
223 private boolean doFireTileBoundChanged = true;
224
225 protected JPanel buildTextPanel() {
226 JPanel pnl = new JPanel(new BorderLayout());
227 HtmlPanel msg = new HtmlPanel();
228 msg.setText(tr("<html>Please select a <strong>range of OSM tiles</strong> at a given zoom level.</html>"));
229 pnl.add(msg);
230 return pnl;
231 }
232
233 protected JPanel buildZoomLevelPanel() {
234 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
235 pnl.add(new JLabel(tr("Zoom level:")));
236 pnl.add(spZoomLevel);
237 spZoomLevel.addChangeListener(new ZomeLevelChangeHandler());
238 spZoomLevel.addChangeListener(tileBoundsBuilder);
239 return pnl;
240 }
241
242 protected JPanel buildTileGridInputPanel() {
243 JPanel pnl = new JPanel(new GridBagLayout());
244 pnl.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
245 GridBagConstraints gc = new GridBagConstraints();
246 gc.anchor = GridBagConstraints.NORTHWEST;
247 gc.insets = new Insets(0, 0, 2, 2);
248
249 gc.gridwidth = 2;
250 gc.gridx = 1;
251 gc.fill = GridBagConstraints.HORIZONTAL;
252 pnl.add(buildZoomLevelPanel(), gc);
253
254 gc.gridwidth = 1;
255 gc.gridy = 1;
256 gc.gridx = 1;
257 pnl.add(new JLabel(tr("from tile")), gc);
258
259 gc.gridx = 2;
260 pnl.add(new JLabel(tr("up to tile")), gc);
261
262 gc.gridx = 0;
263 gc.gridy = 2;
264 gc.weightx = 0.0;
265 pnl.add(new JLabel("X:"), gc);
266
267
268 gc.gridx = 1;
269 gc.weightx = 0.5;
270 pnl.add(tfMinX, gc);
271 valMinX = new TileCoordinateValidator(tfMinX);
272 SelectAllOnFocusGainedDecorator.decorate(tfMinX);
273 tfMinX.addActionListener(tileBoundsBuilder);
274 tfMinX.addFocusListener(tileBoundsBuilder);
275
276 gc.gridx = 2;
277 gc.weightx = 0.5;
278 pnl.add(tfMaxX, gc);
279 valMaxX = new TileCoordinateValidator(tfMaxX);
280 SelectAllOnFocusGainedDecorator.decorate(tfMaxX);
281 tfMaxX.addActionListener(tileBoundsBuilder);
282 tfMaxX.addFocusListener(tileBoundsBuilder);
283
284 gc.gridx = 0;
285 gc.gridy = 3;
286 gc.weightx = 0.0;
287 pnl.add(new JLabel("Y:"), gc);
288
289 gc.gridx = 1;
290 gc.weightx = 0.5;
291 pnl.add(tfMinY, gc);
292 valMinY = new TileCoordinateValidator(tfMinY);
293 SelectAllOnFocusGainedDecorator.decorate(tfMinY);
294 tfMinY.addActionListener(tileBoundsBuilder);
295 tfMinY.addFocusListener(tileBoundsBuilder);
296
297 gc.gridx = 2;
298 gc.weightx = 0.5;
299 pnl.add(tfMaxY, gc);
300 valMaxY = new TileCoordinateValidator(tfMaxY);
301 SelectAllOnFocusGainedDecorator.decorate(tfMaxY);
302 tfMaxY.addActionListener(tileBoundsBuilder);
303 tfMaxY.addFocusListener(tileBoundsBuilder);
304
305 gc.gridy = 4;
306 gc.gridx = 0;
307 gc.gridwidth = 3;
308 gc.weightx = 1.0;
309 gc.weighty = 1.0;
310 gc.fill = GridBagConstraints.BOTH;
311 pnl.add(new JPanel(), gc);
312 return pnl;
313 }
314
315 protected void build() {
316 setLayout(new BorderLayout());
317 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
318 add(buildTextPanel(), BorderLayout.NORTH);
319 add(buildTileGridInputPanel(), BorderLayout.CENTER);
320
321 Set<AWTKeyStroke> forwardKeys = new HashSet<>(getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
322 forwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
323 setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
324 }
325
326 TileGridInputPanel() {
327 build();
328 }
329
330 public void initFromBoundingBox(Bounds bbox) {
331 if (bbox == null)
332 return;
333 TileBounds tb = new TileBounds();
334 tb.zoomLevel = (Integer) spZoomLevel.getValue();
335 tb.min = new Point(
336 Math.max(0, lonToTileX(tb.zoomLevel, bbox.getMinLon())),
337 Math.max(0, latToTileY(tb.zoomLevel, bbox.getMaxLat() - 0.00001))
338 );
339 tb.max = new Point(
340 Math.max(0, lonToTileX(tb.zoomLevel, bbox.getMaxLon())),
341 Math.max(0, latToTileY(tb.zoomLevel, bbox.getMinLat() - 0.00001))
342 );
343 doFireTileBoundChanged = false;
344 setTileBounds(tb);
345 doFireTileBoundChanged = true;
346 }
347
348 public static int latToTileY(int zoom, double lat) {
349 if ((zoom < 3) || (zoom > 18)) return -1;
350 double l = lat / 180 * Math.PI;
351 double pf = Math.log(Math.tan(l) + (1/Math.cos(l)));
352 return (int) ((1 << (zoom-1)) * (Math.PI - pf) / Math.PI);
353 }
354
355 public static int lonToTileX(int zoom, double lon) {
356 if ((zoom < 3) || (zoom > 18)) return -1;
357 return (int) ((1 << (zoom-3)) * (lon + 180.0) / 45.0);
358 }
359
360 public void setTileBounds(TileBounds tileBounds) {
361 tfMinX.setText(Integer.toString(tileBounds.min.x));
362 tfMinY.setText(Integer.toString(tileBounds.min.y));
363 tfMaxX.setText(Integer.toString(tileBounds.max.x));
364 tfMaxY.setText(Integer.toString(tileBounds.max.y));
365 spZoomLevel.setValue(tileBounds.zoomLevel);
366 }
367
368 @Override
369 public void propertyChange(PropertyChangeEvent evt) {
370 if (evt.getPropertyName().equals(TileAddressInputPanel.TILE_BOUNDS_PROP)) {
371 TileBounds tb = (TileBounds) evt.getNewValue();
372 setTileBounds(tb);
373 fireTileBoundsChanged(tb);
374 }
375 }
376
377 protected void fireTileBoundsChanged(TileBounds tb) {
378 if (!doFireTileBoundChanged) return;
379 firePropertyChange(TILE_BOUNDS_PROP, null, tb);
380 }
381
382 class ZomeLevelChangeHandler implements ChangeListener {
383 @Override
384 public void stateChanged(ChangeEvent e) {
385 int zoomLevel = (Integer) spZoomLevel.getValue();
386 valMaxX.setZoomLevel(zoomLevel);
387 valMaxY.setZoomLevel(zoomLevel);
388 valMinX.setZoomLevel(zoomLevel);
389 valMinY.setZoomLevel(zoomLevel);
390 }
391 }
392
393 class TileBoundsBuilder implements ActionListener, FocusListener, ChangeListener {
394 protected void buildTileBounds() {
395 if (!valMaxX.isValid()) return;
396 if (!valMaxY.isValid()) return;
397 if (!valMinX.isValid()) return;
398 if (!valMinY.isValid()) return;
399 Point min = new Point(valMinX.getTileIndex(), valMinY.getTileIndex());
400 Point max = new Point(valMaxX.getTileIndex(), valMaxY.getTileIndex());
401 int zoomlevel = (Integer) spZoomLevel.getValue();
402 TileBounds tb = new TileBounds(min, max, zoomlevel);
403 fireTileBoundsChanged(tb);
404 }
405
406 @Override
407 public void focusGained(FocusEvent e) {
408 /* irrelevant */
409 }
410
411 @Override
412 public void focusLost(FocusEvent e) {
413 buildTileBounds();
414 }
415
416 @Override
417 public void actionPerformed(ActionEvent e) {
418 buildTileBounds();
419 }
420
421 @Override
422 public void stateChanged(ChangeEvent e) {
423 buildTileBounds();
424 }
425 }
426 }
427
428 /**
429 * A panel for entering the address of a single OSM tile at a given zoom level.
430 *
431 */
432 private static class TileAddressInputPanel extends JPanel {
433
434 public static final String TILE_BOUNDS_PROP = TileAddressInputPanel.class.getName() + ".tileBounds";
435
436 private transient TileAddressValidator valTileAddress;
437
438 protected JPanel buildTextPanel() {
439 JPanel pnl = new JPanel(new BorderLayout());
440 HtmlPanel msg = new HtmlPanel();
441 msg.setText(tr("<html>Alternatively you may enter a <strong>tile address</strong> for a single tile "
442 + "in the format <i>zoomlevel/x/y</i>, e.g. <i>15/256/223</i>. Tile addresses "
443 + "in the format <i>zoom,x,y</i> or <i>zoom;x;y</i> are valid too.</html>"));
444 pnl.add(msg);
445 return pnl;
446 }
447
448 protected JPanel buildTileAddressInputPanel() {
449 JPanel pnl = new JPanel(new GridBagLayout());
450 GridBagConstraints gc = new GridBagConstraints();
451 gc.anchor = GridBagConstraints.NORTHWEST;
452 gc.fill = GridBagConstraints.HORIZONTAL;
453 gc.weightx = 0.0;
454 gc.insets = new Insets(0, 0, 2, 2);
455 pnl.add(new JLabel(tr("Tile address:")), gc);
456
457 gc.weightx = 1.0;
458 gc.gridx = 1;
459 JosmTextField tfTileAddress = new JosmTextField();
460 pnl.add(tfTileAddress, gc);
461 valTileAddress = new TileAddressValidator(tfTileAddress);
462 SelectAllOnFocusGainedDecorator.decorate(tfTileAddress);
463
464 gc.weightx = 0.0;
465 gc.gridx = 2;
466 ApplyTileAddressAction applyTileAddressAction = new ApplyTileAddressAction();
467 JButton btn = new JButton(applyTileAddressAction);
468 btn.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
469 pnl.add(btn, gc);
470 tfTileAddress.addActionListener(applyTileAddressAction);
471 return pnl;
472 }
473
474 protected void build() {
475 setLayout(new GridBagLayout());
476 GridBagConstraints gc = new GridBagConstraints();
477 gc.anchor = GridBagConstraints.NORTHWEST;
478 gc.fill = GridBagConstraints.HORIZONTAL;
479 gc.weightx = 1.0;
480 gc.insets = new Insets(0, 0, 5, 0);
481 add(buildTextPanel(), gc);
482
483 gc.gridy = 1;
484 add(buildTileAddressInputPanel(), gc);
485
486 // filler - grab remaining space
487 gc.gridy = 2;
488 gc.fill = GridBagConstraints.BOTH;
489 gc.weighty = 1.0;
490 add(new JPanel(), gc);
491 }
492
493 TileAddressInputPanel() {
494 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
495 build();
496 }
497
498 protected void fireTileBoundsChanged(TileBounds tb) {
499 firePropertyChange(TILE_BOUNDS_PROP, null, tb);
500 }
501
502 class ApplyTileAddressAction extends AbstractAction {
503 ApplyTileAddressAction() {
504 new ImageProvider("apply").getResource().attachImageIcon(this, true);
505 putValue(SHORT_DESCRIPTION, tr("Apply the tile address"));
506 }
507
508 @Override
509 public void actionPerformed(ActionEvent e) {
510 TileBounds tb = valTileAddress.getTileBounds();
511 if (tb != null) {
512 fireTileBoundsChanged(tb);
513 }
514 }
515 }
516 }
517
518 /**
519 * Validates a tile address
520 */
521 private static class TileAddressValidator extends AbstractTextComponentValidator {
522
523 private TileBounds tileBounds;
524
525 TileAddressValidator(JTextComponent tc) {
526 super(tc);
527 }
528
529 @Override
530 public boolean isValid() {
531 String value = getComponent().getText().trim();
532 Matcher m = Pattern.compile("(\\d+)[^\\d]+(\\d+)[^\\d]+(\\d+)").matcher(value);
533 tileBounds = null;
534 if (!m.matches()) return false;
535 int zoom;
536 try {
537 zoom = Integer.parseInt(m.group(1));
538 } catch (NumberFormatException e) {
539 return false;
540 }
541 if (zoom < 0 || zoom > 18) return false;
542
543 int x;
544 try {
545 x = Integer.parseInt(m.group(2));
546 } catch (NumberFormatException e) {
547 return false;
548 }
549 if (x < 0 || x >= Math.pow(2, zoom)) return false;
550 int y;
551 try {
552 y = Integer.parseInt(m.group(3));
553 } catch (NumberFormatException e) {
554 return false;
555 }
556 if (y < 0 || y >= Math.pow(2, zoom)) return false;
557
558 tileBounds = new TileBounds(new Point(x, y), new Point(x, y), zoom);
559 return true;
560 }
561
562 @Override
563 public void validate() {
564 if (isValid()) {
565 feedbackValid(tr("Please enter a tile address"));
566 } else {
567 feedbackInvalid(tr("The current value isn''t a valid tile address", getComponent().getText()));
568 }
569 }
570
571 public TileBounds getTileBounds() {
572 return tileBounds;
573 }
574 }
575
576 /**
577 * Validates the x- or y-coordinate of a tile at a given zoom level.
578 *
579 */
580 private static class TileCoordinateValidator extends AbstractTextComponentValidator {
581 private int zoomLevel;
582 private int tileIndex;
583
584 TileCoordinateValidator(JTextComponent tc) {
585 super(tc);
586 }
587
588 public void setZoomLevel(int zoomLevel) {
589 this.zoomLevel = zoomLevel;
590 validate();
591 }
592
593 @Override
594 public boolean isValid() {
595 String value = getComponent().getText().trim();
596 try {
597 if (value.isEmpty()) {
598 tileIndex = 0;
599 } else {
600 tileIndex = Integer.parseInt(value);
601 }
602 } catch (NumberFormatException e) {
603 return false;
604 }
605 return tileIndex >= 0 && tileIndex < Math.pow(2, zoomLevel);
606 }
607
608 @Override
609 public void validate() {
610 if (isValid()) {
611 feedbackValid(tr("Please enter a tile index"));
612 } else {
613 feedbackInvalid(tr("The current value isn''t a valid tile index for the given zoom level", getComponent().getText()));
614 }
615 }
616
617 public int getTileIndex() {
618 return tileIndex;
619 }
620 }
621
622 /**
623 * Represents a rectangular area of tiles at a given zoom level.
624 */
625 private static final class TileBounds {
626 private Point min;
627 private Point max;
628 private int zoomLevel;
629
630 private TileBounds() {
631 zoomLevel = 0;
632 min = new Point(0, 0);
633 max = new Point(0, 0);
634 }
635
636 private TileBounds(Point min, Point max, int zoomLevel) {
637 this.min = min;
638 this.max = max;
639 this.zoomLevel = zoomLevel;
640 }
641
642 @Override
643 public String toString() {
644 StringBuilder sb = new StringBuilder(24);
645 sb.append("min=").append(min.x).append(',').append(min.y)
646 .append(",max=").append(max.x).append(',').append(max.y)
647 .append(",zoom=").append(zoomLevel);
648 return sb.toString();
649 }
650 }
651
652 /**
653 * The map view used in this bounding box chooser
654 */
655 private static final class TileBoundsMapView extends JosmMapViewer {
656 private Point min;
657 private Point max;
658
659 private TileBoundsMapView() {
660 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
661 }
662
663 public void setBoundingBox(Bounds bbox) {
664 if (bbox == null) {
665 min = null;
666 max = null;
667 } else {
668 Point p1 = tileSource.latLonToXY(bbox.getMinLat(), bbox.getMinLon(), MAX_ZOOM);
669 Point p2 = tileSource.latLonToXY(bbox.getMaxLat(), bbox.getMaxLon(), MAX_ZOOM);
670
671 min = new Point(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y));
672 max = new Point(Math.max(p1.x, p2.x), Math.max(p1.y, p2.y));
673 }
674 repaint();
675 }
676
677 private Point getTopLeftCoordinates() {
678 return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
679 }
680
681 /**
682 * Draw the map.
683 */
684 @Override
685 public void paint(Graphics g) {
686 super.paint(g);
687 if (min == null || max == null) return;
688 int zoomDiff = MAX_ZOOM - zoom;
689 Point tlc = getTopLeftCoordinates();
690 int xMin = (min.x >> zoomDiff) - tlc.x;
691 int yMin = (min.y >> zoomDiff) - tlc.y;
692 int xMax = (max.x >> zoomDiff) - tlc.x;
693 int yMax = (max.y >> zoomDiff) - tlc.y;
694
695 int w = xMax - xMin;
696 int h = yMax - yMin;
697 g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
698 g.fillRect(xMin, yMin, w, h);
699
700 g.setColor(Color.BLACK);
701 g.drawRect(xMin, yMin, w, h);
702 }
703 }
704}
Note: See TracBrowser for help on using the repository browser.