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

Last change on this file since 13691 was 13426, checked in by Don-vip, 6 years ago

fix typo: setZoomContolsVisible => setZoomControlsVisible

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