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

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

global replacement of e.printStackTrace() by Main.error(e)

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