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

Last change on this file since 9012 was 9012, checked in by Klumbumbus, 8 years ago

fix #12100 - improve message string

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