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

Last change on this file since 11893 was 11893, checked in by Don-vip, 7 years ago

sonar - squid:S1126 - Return of boolean expressions should not be wrapped into an "if-then-else" statement

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