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

Last change on this file since 6822 was 6822, checked in by bastiK, 10 years ago

see #8902 - Small performance enhancements / coding style (patch by shinigami, updated, modified)

stringbuilder.diff: StringBuffer => StringBuilder

  • Property svn:eol-style set to native
File size: 25.5 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 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 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<MapMarker>(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 static private class TileGridInputPanel extends JPanel implements PropertyChangeListener{
220 static public 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 TileCoordinateValidator valMaxY;
227 private TileCoordinateValidator valMinY;
228 private TileCoordinateValidator valMaxX;
229 private TileCoordinateValidator valMinX;
230 private JSpinner spZoomLevel;
231 private 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<AWTKeyStroke>(getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
331 forwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
332 setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,forwardKeys);
333 }
334
335 public 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) {/* irrelevant */}
417
418 @Override
419 public void focusLost(FocusEvent e) {
420 buildTileBounds();
421 }
422
423 @Override
424 public void actionPerformed(ActionEvent e) {
425 buildTileBounds();
426 }
427
428 @Override
429 public void stateChanged(ChangeEvent e) {
430 buildTileBounds();
431 }
432 }
433 }
434
435 /**
436 * A panel for entering the address of a single OSM tile at a given zoom level.
437 *
438 */
439 static private class TileAddressInputPanel extends JPanel {
440
441 static public final String TILE_BOUNDS_PROP = TileAddressInputPanel.class.getName() + ".tileBounds";
442
443 private JosmTextField tfTileAddress;
444 private TileAddressValidator valTileAddress;
445
446 protected JPanel buildTextPanel() {
447 JPanel pnl = new JPanel(new BorderLayout());
448 HtmlPanel msg = new HtmlPanel();
449 msg.setText(tr("<html>Alternatively you may enter a <strong>tile address</strong> for a single tile "
450 + "in the format <i>zoomlevel/x/y</i>, i.e. <i>15/256/223</i>. Tile addresses "
451 + "in the format <i>zoom,x,y</i> or <i>zoom;x;y</i> are valid too.</html>"));
452 pnl.add(msg);
453 return pnl;
454 }
455
456 protected JPanel buildTileAddressInputPanel() {
457 JPanel pnl = new JPanel(new GridBagLayout());
458 GridBagConstraints gc = new GridBagConstraints();
459 gc.anchor = GridBagConstraints.NORTHWEST;
460 gc.fill = GridBagConstraints.HORIZONTAL;
461 gc.weightx = 0.0;
462 gc.insets = new Insets(0,0,2,2);
463 pnl.add(new JLabel(tr("Tile address:")), gc);
464
465 gc.weightx = 1.0;
466 gc.gridx = 1;
467 pnl.add(tfTileAddress = new JosmTextField(), gc);
468 valTileAddress = new TileAddressValidator(tfTileAddress);
469 SelectAllOnFocusGainedDecorator.decorate(tfTileAddress);
470
471 gc.weightx = 0.0;
472 gc.gridx = 2;
473 ApplyTileAddressAction applyTileAddressAction = new ApplyTileAddressAction();
474 JButton btn = new JButton(applyTileAddressAction);
475 btn.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
476 pnl.add(btn, gc);
477 tfTileAddress.addActionListener(applyTileAddressAction);
478 return pnl;
479 }
480
481 protected void build() {
482 setLayout(new GridBagLayout());
483 GridBagConstraints gc = new GridBagConstraints();
484 gc.anchor = GridBagConstraints.NORTHWEST;
485 gc.fill = GridBagConstraints.HORIZONTAL;
486 gc.weightx = 1.0;
487 gc.insets = new Insets(0,0,5,0);
488 add(buildTextPanel(), gc);
489
490 gc.gridy = 1;
491 add(buildTileAddressInputPanel(), gc);
492
493 // filler - grab remaining space
494 gc.gridy = 2;
495 gc.fill = GridBagConstraints.BOTH;
496 gc.weighty = 1.0;
497 add(new JPanel(), gc);
498 }
499
500 public TileAddressInputPanel() {
501 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
502 build();
503 }
504
505 protected void fireTileBoundsChanged(TileBounds tb){
506 firePropertyChange(TILE_BOUNDS_PROP, null, tb);
507 }
508
509 class ApplyTileAddressAction extends AbstractAction {
510 public ApplyTileAddressAction() {
511 putValue(SMALL_ICON, ImageProvider.get("apply"));
512 putValue(SHORT_DESCRIPTION, tr("Apply the tile address"));
513 }
514
515 @Override
516 public void actionPerformed(ActionEvent e) {
517 TileBounds tb = valTileAddress.getTileBounds();
518 if (tb != null) {
519 fireTileBoundsChanged(tb);
520 }
521 }
522 }
523 }
524
525 /**
526 * Validates a tile address
527 */
528 static private class TileAddressValidator extends AbstractTextComponentValidator {
529
530 private TileBounds tileBounds = null;
531
532 public TileAddressValidator(JTextComponent tc) throws IllegalArgumentException {
533 super(tc);
534 }
535
536 @Override
537 public boolean isValid() {
538 String value = getComponent().getText().trim();
539 Matcher m = Pattern.compile("(\\d+)[^\\d]+(\\d+)[^\\d]+(\\d+)").matcher(value);
540 tileBounds = null;
541 if (!m.matches()) return false;
542 int zoom;
543 try {
544 zoom = Integer.parseInt(m.group(1));
545 } catch(NumberFormatException e){
546 return false;
547 }
548 if (zoom < 0 || zoom > 18) return false;
549
550 int x;
551 try {
552 x = Integer.parseInt(m.group(2));
553 } catch(NumberFormatException e){
554 return false;
555 }
556 if (x < 0 || x >= Math.pow(2, zoom)) return false;
557 int y;
558 try {
559 y = Integer.parseInt(m.group(3));
560 } catch(NumberFormatException e){
561 return false;
562 }
563 if (y < 0 || y >= Math.pow(2, zoom)) return false;
564
565 tileBounds = new TileBounds(new Point(x,y), new Point(x,y), zoom);
566 return true;
567 }
568
569 @Override
570 public void validate() {
571 if (isValid()) {
572 feedbackValid(tr("Please enter a tile address"));
573 } else {
574 feedbackInvalid(tr("The current value isn''t a valid tile address", getComponent().getText()));
575 }
576 }
577
578 public TileBounds getTileBounds() {
579 return tileBounds;
580 }
581 }
582
583 /**
584 * Validates the x- or y-coordinate of a tile at a given zoom level.
585 *
586 */
587 static private class TileCoordinateValidator extends AbstractTextComponentValidator {
588 private int zoomLevel;
589 private int tileIndex;
590
591 public TileCoordinateValidator(JTextComponent tc) throws IllegalArgumentException {
592 super(tc);
593 }
594
595 public void setZoomLevel(int zoomLevel) {
596 this.zoomLevel = zoomLevel;
597 validate();
598 }
599
600 @Override
601 public boolean isValid() {
602 String value = getComponent().getText().trim();
603 try {
604 if (value.isEmpty()) {
605 tileIndex = 0;
606 } else {
607 tileIndex = Integer.parseInt(value);
608 }
609 } catch(NumberFormatException e) {
610 return false;
611 }
612 if (tileIndex < 0 || tileIndex >= Math.pow(2, zoomLevel)) return false;
613
614 return true;
615 }
616
617 @Override
618 public void validate() {
619 if (isValid()) {
620 feedbackValid(tr("Please enter a tile index"));
621 } else {
622 feedbackInvalid(tr("The current value isn''t a valid tile index for the given zoom level", getComponent().getText()));
623 }
624 }
625
626 public int getTileIndex() {
627 return tileIndex;
628 }
629 }
630
631 /**
632 * Represents a rectangular area of tiles at a given zoom level.
633 *
634 */
635 static private class TileBounds {
636 public Point min;
637 public Point max;
638 public int zoomLevel;
639
640 public TileBounds() {
641 zoomLevel = 0;
642 min = new Point(0,0);
643 max = new Point(0,0);
644 }
645
646 public TileBounds(Point min, Point max, int zoomLevel) {
647 this.min = min;
648 this.max = max;
649 this.zoomLevel = zoomLevel;
650 }
651
652 @Override
653 public String toString() {
654 StringBuilder sb = new StringBuilder();
655 sb.append("min=").append(min.x).append(",").append(min.y).append(",");
656 sb.append("max=").append(max.x).append(",").append(max.y).append(",");
657 sb.append("zoom=").append(zoomLevel);
658 return sb.toString();
659 }
660 }
661
662 /**
663 * The map view used in this bounding box chooser
664 */
665 static private class TileBoundsMapView extends JMapViewer {
666 private Point min;
667 private Point max;
668
669 public TileBoundsMapView() {
670 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
671 TileLoader loader = tileController.getTileLoader();
672 if (loader instanceof OsmTileLoader) {
673 ((OsmTileLoader)loader).headers.put("User-Agent", Version.getInstance().getFullAgentString());
674 }
675 }
676
677 public void setBoundingBox(Bounds bbox){
678 if (bbox == null) {
679 min = null;
680 max = null;
681 } else {
682 int y1 = tileSource.LatToY(bbox.getMinLat(), MAX_ZOOM);
683 int y2 = tileSource.LatToY(bbox.getMaxLat(), MAX_ZOOM);
684 int x1 = tileSource.LonToX(bbox.getMinLon(), MAX_ZOOM);
685 int x2 = tileSource.LonToX(bbox.getMaxLon(), MAX_ZOOM);
686
687 min = new Point(Math.min(x1, x2), Math.min(y1, y2));
688 max = new Point(Math.max(x1, x2), Math.max(y1, y2));
689 }
690 repaint();
691 }
692
693 protected Point getTopLeftCoordinates() {
694 return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
695 }
696
697 /**
698 * Draw the map.
699 */
700 @Override
701 public void paint(Graphics g) {
702 try {
703 super.paint(g);
704 if (min == null || max == null) return;
705 int zoomDiff = MAX_ZOOM - zoom;
706 Point tlc = getTopLeftCoordinates();
707 int x_min = (min.x >> zoomDiff) - tlc.x;
708 int y_min = (min.y >> zoomDiff) - tlc.y;
709 int x_max = (max.x >> zoomDiff) - tlc.x;
710 int y_max = (max.y >> zoomDiff) - tlc.y;
711
712 int w = x_max - x_min;
713 int h = y_max - y_min;
714 g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
715 g.fillRect(x_min, y_min, w, h);
716
717 g.setColor(Color.BLACK);
718 g.drawRect(x_min, y_min, w, h);
719 } catch (Exception e) {
720 Main.error(e);
721 }
722 }
723 }
724}
Note: See TracBrowser for help on using the repository browser.